# 程序打包.py
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import subprocess
import os
import sys
import threadingclass PyToLinuxEXE:def __init__(self):self.root = tk.Tk()self.root.title("Python程序打包工具")self.root.geometry("900x700") # 增加窗口高度以容纳新功能# 设置背景色为淡黄色self.bg_color = "#FFFACD" # 淡黄色self.root.configure(bg=self.bg_color)self.source_file = tk.StringVar()self.output_dir = tk.StringVar()self.output_name = tk.StringVar()self.one_file = tk.BooleanVar(value=True)self.console = tk.BooleanVar(value=False)self.icon_file = tk.StringVar()self.packaging_tool = tk.StringVar(value="pyinstaller") # 默认打包工具self.compression_level = tk.StringVar(value="normal") # 压缩级别# 设置默认字体self.default_font = ("fangsong ti", 12)# 设置全局字体self.set_global_font()# 设置糖果色self.candy_colors = {"button_bg": "#FFB6C1", # 粉红色"button_fg": "#8B008B", # 深洋红色"button_active_bg": "#FF69B4", # 热粉红色"browse_button_bg": "#98FB98", # 淡绿色"browse_button_fg": "#006400", # 深绿色"browse_button_active_bg": "#00FF7F", # 春绿色"action_button_bg": "#87CEFA", # 淡蓝色"action_button_fg": "#000080", # 海军蓝"action_button_active_bg": "#4169E1", # 皇家蓝"info_button_bg": "#FFD700", # 金色"info_button_fg": "#8B4513", # saddlebrown"info_button_active_bg": "#FFA500" # 橙色}# 设置输出默认值self.output_dir.set(os.path.expanduser("D:\\test\\"))# 设置源文件默认值(如果文件存在)default_source = "/home/huanghe/PycharmProjects/pythonEMY"if os.path.exists(default_source):self.source_file.set(default_source)# 自动设置输出文件名self.output_name.set(os.path.splitext(os.path.basename(default_source))[0])# 设置图标文件默认值(如果文件存在)default_icon = "/home/huanghe/test/file.ico"if os.path.exists(default_icon):self.icon_file.set(default_icon)# 打包工具信息self.tool_info = {"pyinstaller": {"env_requirements": "需要安装PyInstaller: pip install pyinstaller","compression_options": {"无压缩": "","普通压缩": "--noupx","最大压缩": ""},"console_options": {"显示控制台": "--console","隐藏控制台": "--noconsole"}},"cx_freeze": {"env_requirements": "需要安装cx_Freeze: pip install cx_Freeze","compression_options": {"无压缩": "","普通压缩": "","最大压缩": ""},"console_options": {"显示控制台": "","隐藏控制台": "--base-name=Win32GUI"}},"nuitka": {"env_requirements": "需要安装Nuitka和C编译器(GCC): pip install nuitka","compression_options": {"无压缩": "","普通压缩": "","最大压缩": "--lto"},"console_options": {"显示控制台": "","隐藏控制台": "--windows-disable-console"}},"briefcase": {"env_requirements": "需要安装Briefcase: pip install briefcase","compression_options": {"无压缩": "","普通压缩": "","最大压缩": ""},"console_options": {"显示控制台": "","隐藏控制台": ""}}}self.create_widgets()def set_global_font(self):# 设置默认字体self.default_font = ("fangsong ti", 12)# 配置根窗口的默认字体self.root.option_add("*Font", self.default_font)# 创建自定义样式style = ttk.Style()style.configure(".", font=self.default_font, background=self.bg_color)style.configure("TButton", font=self.default_font)style.configure("TLabel", font=self.default_font, background=self.bg_color)style.configure("TCheckbutton", font=self.default_font, background=self.bg_color)style.configure("TEntry", font=self.default_font)style.configure("TFrame", background=self.bg_color)style.configure("TRadiobutton", font=self.default_font, background=self.bg_color)def create_widgets(self):main_frame = tk.Frame(self.root, bg=self.bg_color)main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)# 控制区域control_frame = tk.Frame(main_frame, bg=self.bg_color)control_frame.pack(fill=tk.X, pady=3)# 源文件选择input_frame = tk.Frame(control_frame, bg=self.bg_color)input_frame.pack(fill=tk.X, pady=2)tk.Label(input_frame, text="源文件:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)tk.Entry(input_frame, textvariable=self.source_file, font=self.default_font, bg="white").pack(side=tk.LEFT,fill=tk.X,expand=True,padx=3)tk.Button(input_frame, text="浏览", command=self.browse_source, width=8,bg=self.candy_colors["browse_button_bg"], fg=self.candy_colors["browse_button_fg"],activebackground=self.candy_colors["browse_button_active_bg"], font=self.default_font,relief=tk.RAISED, bd=2).pack(side=tk.LEFT, padx=3)# 输出目录选择output_frame = tk.Frame(control_frame, bg=self.bg_color)output_frame.pack(fill=tk.X, pady=2)tk.Label(output_frame, text="输出目录:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)tk.Entry(output_frame, textvariable=self.output_dir, font=self.default_font, bg="white").pack(side=tk.LEFT,fill=tk.X,expand=True,padx=3)tk.Button(output_frame, text="浏览", command=self.browse_output, width=8,bg=self.candy_colors["browse_button_bg"], fg=self.candy_colors["browse_button_fg"],activebackground=self.candy_colors["browse_button_active_bg"], font=self.default_font,relief=tk.RAISED, bd=2).pack(side=tk.LEFT, padx=3)# 输出文件名name_frame = tk.Frame(control_frame, bg=self.bg_color)name_frame.pack(fill=tk.X, pady=2)tk.Label(name_frame, text="输出名称:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)tk.Entry(name_frame, textvariable=self.output_name, font=self.default_font, bg="white").pack(side=tk.LEFT,fill=tk.X,expand=True,padx=3)# 打包工具选择tool_frame = tk.Frame(control_frame, bg=self.bg_color)tool_frame.pack(fill=tk.X, pady=2)tk.Label(tool_frame, text="打包工具:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)tool_combo = ttk.Combobox(tool_frame, textvariable=self.packaging_tool,values=["pyinstaller", "cx_freeze", "nuitka", "briefcase"],state="readonly", font=self.default_font, width=15)tool_combo.pack(side=tk.LEFT, padx=3)tool_combo.set("pyinstaller") # 设置默认值tool_combo.bind("<<ComboboxSelected>>", self.on_tool_change)# 添加环境信息按钮tk.Button(tool_frame, text="环境要求", command=self.show_env_info, width=8,bg=self.candy_colors["info_button_bg"], fg=self.candy_colors["info_button_fg"],activebackground=self.candy_colors["info_button_active_bg"], font=self.default_font,relief=tk.RAISED, bd=2).pack(side=tk.LEFT, padx=3)# 选项区域options_frame = tk.Frame(control_frame, bg=self.bg_color)options_frame.pack(fill=tk.X, pady=10)tk.Label(options_frame, text="打包选项:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)options_container = tk.Frame(options_frame, bg=self.bg_color)options_container.pack(side=tk.LEFT, fill=tk.X, expand=True)tk.Checkbutton(options_container, text="生成单个文件", variable=self.one_file,font=self.default_font, bg=self.bg_color, selectcolor="white").pack(side=tk.LEFT, padx=10)tk.Checkbutton(options_container, text="无控制台窗口", variable=self.console,font=self.default_font, bg=self.bg_color, selectcolor="white").pack(side=tk.LEFT, padx=10)# 压缩级别选择compression_frame = tk.Frame(control_frame, bg=self.bg_color)compression_frame.pack(fill=tk.X, pady=2)tk.Label(compression_frame, text="压缩级别:", width=10, anchor=tk.W, font=self.default_font,bg=self.bg_color).pack(side=tk.LEFT)compression_options = tk.Frame(compression_frame, bg=self.bg_color)compression_options.pack(side=tk.LEFT, fill=tk.X, expand=True)tk.Radiobutton(compression_options, text="无压缩", variable=self.compression_level, value="none",font=self.default_font, bg=self.bg_color, selectcolor="white").pack(side=tk.LEFT, padx=5)tk.Radiobutton(compression_options, text="普通压缩", variable=self.compression_level, value="normal",font=self.default_font, bg=self.bg_color, selectcolor="white").pack(side=tk.LEFT, padx=5)tk.Radiobutton(compression_options, text="最大压缩", variable=self.compression_level, value="max",font=self.default_font, bg=self.bg_color, selectcolor="white").pack(side=tk.LEFT, padx=5)# 图标文件选择icon_frame = tk.Frame(control_frame, bg=self.bg_color)icon_frame.pack(fill=tk.X, pady=2)tk.Label(icon_frame, text="图标文件:", width=10, anchor=tk.W, font=self.default_font, bg=self.bg_color).pack(side=tk.LEFT)tk.Entry(icon_frame, textvariable=self.icon_file, font=self.default_font, bg="white").pack(side=tk.LEFT,fill=tk.X,expand=True, padx=3)tk.Button(icon_frame, text="浏览", command=self.browse_icon, width=8,bg=self.candy_colors["browse_button_bg"], fg=self.candy_colors["browse_button_fg"],activebackground=self.candy_colors["browse_button_active_bg"], font=self.default_font,relief=tk.RAISED, bd=2).pack(side=tk.LEFT, padx=3)# 按钮区域button_frame = tk.Frame(control_frame, bg=self.bg_color)button_frame.pack(fill=tk.X, pady=10)self.pack_button = tk.Button(button_frame, text="开始打包", command=self.start_packaging,bg=self.candy_colors["action_button_bg"], fg=self.candy_colors["action_button_fg"],activebackground=self.candy_colors["action_button_active_bg"],font=self.default_font, relief=tk.RAISED, bd=2)self.pack_button.pack(side=tk.RIGHT, padx=5)tk.Button(button_frame, text="退出", command=self.root.quit,bg=self.candy_colors["button_bg"], fg=self.candy_colors["button_fg"],activebackground=self.candy_colors["button_active_bg"],font=self.default_font, relief=tk.RAISED, bd=2).pack(side=tk.RIGHT, padx=5)# 内容区域content_frame = tk.Frame(main_frame, bg=self.bg_color)content_frame.pack(fill=tk.BOTH, expand=True, pady=5)# 日志区域log_container = tk.Frame(content_frame, bg=self.bg_color)log_container.pack(fill=tk.BOTH, expand=True, padx=2)tk.Label(log_container, text="打包日志", font=self.default_font, bg=self.bg_color).pack(anchor=tk.W)log_frame = tk.Frame(log_container, bg=self.bg_color)log_frame.pack(fill=tk.BOTH, expand=True)self.log_text = tk.Text(log_frame, height=20, font=self.default_font, bg="white")scrollbar = tk.Scrollbar(log_frame, command=self.log_text.yview)self.log_text.configure(yscrollcommand=scrollbar.set)self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)scrollbar.pack(side=tk.RIGHT, fill=tk.Y)# 进度条self.progress = ttk.Progressbar(main_frame, mode='indeterminate')self.progress.pack(fill=tk.X, padx=5, pady=5)def on_tool_change(self, event):"""当打包工具改变时更新界面提示"""tool = self.packaging_tool.get()self.log_message(f"已选择打包工具: {tool}")self.log_message(f"环境要求: {self.tool_info[tool]['env_requirements']}")def show_env_info(self):"""显示当前打包工具的环境要求"""tool = self.packaging_tool.get()info = self.tool_info[tool]["env_requirements"]messagebox.showinfo(f"{tool}环境要求", info)def browse_source(self):filename = filedialog.askopenfilename(title="选择Python文件",filetypes=[("Python files", "*.py"), ("All files", "*.*")])if filename:self.source_file.set(filename)# 自动设置输出文件名if not self.output_name.get():self.output_name.set(os.path.splitext(os.path.basename(filename))[0])def browse_output(self):directory = filedialog.askdirectory(title="选择输出目录")if directory:self.output_dir.set(directory)def browse_icon(self):filename = filedialog.askopenfilename(title="选择图标文件",filetypes=[("Icon files", "*.ico *.png"), ("All files", "*.*")])if filename:self.icon_file.set(filename)def log_message(self, message):self.log_text.insert(tk.END, message + "\n")self.log_text.see(tk.END)self.root.update_idletasks()def start_packaging(self):# 在后台线程中执行打包操作thread = threading.Thread(target=self.package_executable)thread.daemon = Truethread.start()def package_executable(self):# 禁用打包按钮并开始进度条self.pack_button.config(state=tk.DISABLED)self.progress.start()try:# 检查输入source_file = self.source_file.get()if not source_file:messagebox.showwarning("警告", "请选择源文件")returnif not os.path.exists(source_file):messagebox.showwarning("警告", "源文件不存在")returnoutput_dir = self.output_dir.get()if not output_dir:messagebox.showwarning("警告", "请选择输出目录")returnif not os.path.exists(output_dir):os.makedirs(output_dir)output_name = self.output_name.get()if not output_name:output_name = os.path.splitext(os.path.basename(source_file))[0]# 获取选择的打包工具tool = self.packaging_tool.get()# 根据选择的工具构建命令if tool == "pyinstaller":cmd = self.build_pyinstaller_command(source_file, output_dir, output_name)elif tool == "cx_freeze":cmd = self.build_cx_freeze_command(source_file, output_dir, output_name)elif tool == "nuitka":cmd = self.build_nuitka_command(source_file, output_dir, output_name)elif tool == "briefcase":cmd = self.build_briefcase_command(source_file, output_dir, output_name)else:messagebox.showerror("错误", f"不支持的打包工具: {tool}")returnself.log_message(f"使用工具: {tool}")self.log_message("执行命令: " + " ".join(cmd))self.log_message("开始打包...")# 执行命令process = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,universal_newlines=True,cwd=os.path.dirname(source_file) if os.path.isfile(source_file) else os.getcwd())# 实时显示输出for line in process.stdout:self.log_message(line.strip())process.wait()if process.returncode == 0:self.log_message("打包完成!")messagebox.showinfo("成功", "可执行文件已生成")else:self.log_message("打包失败!")messagebox.showerror("错误", "打包过程中出现错误")except Exception as e:self.log_message(f"发生错误: {str(e)}")messagebox.showerror("错误", f"发生错误: {str(e)}")finally:# 重新启用打包按钮并停止进度条self.progress.stop()self.pack_button.config(state=tk.NORMAL)def build_pyinstaller_command(self, source_file, output_dir, output_name):"""构建PyInstaller命令"""cmd = [sys.executable, "-m", "PyInstaller"]# 添加选项if self.one_file.get():cmd.append("--onefile")# 控制台选项if self.console.get():cmd.append("--noconsole")else:cmd.append("--console")# 压缩选项compression = self.compression_level.get()if compression == "none":cmd.append("--noupx")elif compression == "max":# PyInstaller默认使用UPX压缩,最大压缩需要外部UPX工具cmd.append("--upx-compress")if self.icon_file.get():icon_file = self.icon_file.get()if os.path.exists(icon_file):cmd.extend(["--icon", icon_file])# 设置输出目录和名称cmd.extend(["--distpath", output_dir])cmd.extend(["--workpath", os.path.join(output_dir, "build")])cmd.extend(["--specpath", output_dir])cmd.extend(["--name", output_name])# 添加源文件cmd.append(source_file)return cmddef build_cx_freeze_command(self, source_file, output_dir, output_name):"""构建cx_Freeze命令"""# cx_Freeze需要使用setup.py文件,这里创建一个临时的setup_content = f"""
from cx_Freeze import setup, Executable
import sysbuild_options = {{"packages": [], "excludes": []}}base = None
if sys.platform == "win32":base = "Win32GUI" if {str(self.console.get()).lower()} else "Console"executables = [Executable("{source_file}", base=base, target_name="{output_name}")
]setup(name="{output_name}",version="1.0",description="",options={{"build_exe": build_options}},executables=executables
)
"""setup_path = os.path.join(os.path.dirname(source_file), "setup_temp.py")with open(setup_path, "w") as f:f.write(setup_content)cmd = [sys.executable, "setup_temp.py", "build"]cmd.extend(["--build-exe", output_dir])return cmddef build_nuitka_command(self, source_file, output_dir, output_name):"""构建Nuitka命令"""cmd = [sys.executable, "-m", "nuitka", "--standalone", "--enable-plugin=tk-inter"]# 添加选项cmd.extend(["--output-dir=" + output_dir])if self.one_file.get():cmd.append("--onefile")# 控制台选项if self.console.get():cmd.append("--windows-disable-console")# 压缩选项compression = self.compression_level.get()if compression == "max":cmd.append("--lto")# 解决Anaconda环境问题cmd.append("--static-libpython=no")# 优化和清理选项cmd.append("--remove-output")cmd.append("--jobs=4")cmd.append("--clean-cache=all")# 处理matplotlib警告cmd.append("--enable-plugin=no-qt")# Linux图标处理if self.icon_file.get():icon_file = self.icon_file.get()if os.path.exists(icon_file):if icon_file.lower().endswith(('.png', '.ico', '.jpg', '.jpeg')):cmd.extend(["--linux-onefile-icon=" + icon_file])# 输出文件名cmd.extend(["--output-filename=" + output_name])# 添加源文件cmd.append(source_file)return cmddef build_briefcase_command(self, source_file, output_dir, output_name):"""构建Briefcase命令"""# Briefcase需要预先配置,这里提供一个基础命令cmd = [sys.executable, "-m", "briefcase", "create"]# 设置输出目录cmd.extend(["--output", output_dir])return cmddef run(self):self.root.mainloop()if __name__ == "__main__":app = PyToLinuxEXE()app.run()