'''
制作一个名为elf打包的linux环境python程序,通过tktinter输入参数,界面尺寸1200*850,底色淡黄色,按键是糖果绿色,字体:fangsong ti,字号12号。注意避免版本冲突。提前配置nuitka 参数如下。
--standalone:生成独立的可执行文件,不依赖外部库1。
--follow-imports:自动包含所有导入的模块1。
--recurse-all:递归包含所有资源文件1。
Anaconda环境中缺少静态Python库。需要在命令中添加 --static-libpython=no 选项
并使用以下优化选项
性能优化 :--follow-imports、--recurse-all、--remove-output(生成后删除临时目录)
'''
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import subprocess
import json
import threading
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, scrolledtext
class ElfPacker:
def __init__(self, root):
self.root = root
self.root.title("ELF打包工具")
self.root.geometry("1200x850")
self.root.configure(bg='#FFFACD') # 淡黄色背景
# 设置字体
self.font = ('fangsong ti', 12)
# 当前工作目录
self.current_dir = os.getcwd()
# 配置文件路径
self.config_file = os.path.expanduser("~/.elf_packer_config.json")
# 窗口置顶选项(放在create_ui之前)
self.topmost_var = tk.BooleanVar(value=False)
# 加载配置
self.load_config()
# 创建UI
self.create_ui()
# 绑定快捷键
self.root.bind('<Control-q>', self.quit_app)
self.root.bind('<Control-s>', self.save_config)
# 设置窗口图标
try:
self.root.iconbitmap()
except:
pass
def create_ui(self):
"""创建主界面"""
# 主框架
main_frame = tk.Frame(self.root, bg='#FFFACD')
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建左右两栏
left_frame = tk.Frame(main_frame, bg='#FFFACD')
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5)
right_frame = tk.Frame(main_frame, bg='#FFFACD')
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5)
# ==================== 左侧:基本配置 ====================
# 标题
title_label = tk.Label(
left_frame,
text="基本配置",
font=(self.font[0], 14, 'bold'),
bg='#FFFACD'
)
title_label.pack(anchor=tk.W, pady=(0, 10))
# Python脚本路径
script_frame = tk.Frame(left_frame, bg='#FFFACD')
script_frame.pack(fill=tk.X, pady=5)
tk.Label(script_frame, text="Python脚本:", font=self.font, bg='#FFFACD', width=12, anchor=tk.W).pack(side=tk.LEFT)
self.script_path = tk.Entry(script_frame, font=self.font, bg='white')
self.script_path.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
browse_btn = tk.Button(
script_frame,
text="浏览",
font=self.font,
bg='#98FB98',
command=self.browse_script
)
browse_btn.pack(side=tk.RIGHT, padx=2)
# 输出目录
output_frame = tk.Frame(left_frame, bg='#FFFACD')
output_frame.pack(fill=tk.X, pady=5)
tk.Label(output_frame, text="输出目录:", font=self.font, bg='#FFFACD', width=12, anchor=tk.W).pack(side=tk.LEFT)
self.output_dir = tk.Entry(output_frame, font=self.font, bg='white')
self.output_dir.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.output_dir.insert(0, "/home/huanghe/test/xuanzhuan")
browse_output_btn = tk.Button(
output_frame,
text="浏览",
font=self.font,
bg='#98FB98',
command=self.browse_output
)
browse_output_btn.pack(side=tk.RIGHT, padx=2)
# 输出文件名
name_frame = tk.Frame(left_frame, bg='#FFFACD')
name_frame.pack(fill=tk.X, pady=5)
tk.Label(name_frame, text="输出名称:", font=self.font, bg='#FFFACD', width=12, anchor=tk.W).pack(side=tk.LEFT)
self.output_name = tk.Entry(name_frame, font=self.font, bg='white')
self.output_name.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.output_name.insert(0, "快捷地址")
# 分隔线
separator = ttk.Separator(left_frame, orient='horizontal')
separator.pack(fill=tk.X, pady=10)
# ==================== 左侧:Nuitka选项 ====================
options_label = tk.Label(
left_frame,
text="Nuitka选项",
font=(self.font[0], 14, 'bold'),
bg='#FFFACD'
)
options_label.pack(anchor=tk.W, pady=(0, 10))
# 复选框选项
self.options = {
'standalone': tk.BooleanVar(value=True),
'follow_imports': tk.BooleanVar(value=True),
'remove_output': tk.BooleanVar(value=True),
'onefile': tk.BooleanVar(value=True),
'enable_plugin': tk.BooleanVar(value=True),
'show_progress': tk.BooleanVar(value=True),
'static_libpython': tk.BooleanVar(value=False), # 新增:禁用静态Python库
'lto': tk.BooleanVar(value=False)
}
checkbox_frame = tk.Frame(left_frame, bg='#FFFACD')
checkbox_frame.pack(fill=tk.X, pady=5)
row1 = tk.Frame(checkbox_frame, bg='#FFFACD')
row1.pack(fill=tk.X, pady=2)
tk.Checkbutton(row1, text="--standalone (独立可执行文件)", variable=self.options['standalone'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
tk.Checkbutton(row1, text="--follow-imports (包含导入模块)", variable=self.options['follow_imports'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
row2 = tk.Frame(checkbox_frame, bg='#FFFACD')
row2.pack(fill=tk.X, pady=2)
tk.Checkbutton(row2, text="--remove-output (删除临时目录)", variable=self.options['remove_output'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
tk.Checkbutton(row2, text="--onefile (单文件模式)", variable=self.options['onefile'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
row3 = tk.Frame(checkbox_frame, bg='#FFFACD')
row3.pack(fill=tk.X, pady=2)
tk.Checkbutton(row3, text="--enable-plugin=tk-inter (启用tkinter插件)", variable=self.options['enable_plugin'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
tk.Checkbutton(row3, text="--show-progress (显示进度)", variable=self.options['show_progress'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
row4 = tk.Frame(checkbox_frame, bg='#FFFACD')
row4.pack(fill=tk.X, pady=2)
tk.Checkbutton(row4, text="--static-libpython=no (禁用静态Python库)", variable=self.options['static_libpython'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
tk.Checkbutton(row4, text="--lto=no (禁用链接时优化)", variable=self.options['lto'],
font=self.font, bg='#FFFACD').pack(side=tk.LEFT, padx=5)
# 其他参数
other_frame = tk.Frame(left_frame, bg='#FFFACD')
other_frame.pack(fill=tk.X, pady=10)
tk.Label(other_frame, text="其他参数:", font=self.font, bg='#FFFACD').pack(anchor=tk.W)
self.other_args = tk.Entry(other_frame, font=self.font, bg='white')
self.other_args.pack(fill=tk.X, pady=2)
self.other_args.insert(0, "--jobs=4")
# ==================== 右侧:执行控制 ====================
# 打包按钮
pack_btn = tk.Button(
right_frame,
text="开始打包",
font=(self.font[0], 14, 'bold'),
bg='#98FB98',
height=2,
command=self.start_packing
)
pack_btn.pack(fill=tk.X, pady=10)
# 保存配置按钮
save_btn = tk.Button(
right_frame,
text="保存配置",
font=self.font,
bg='#FFB6C1',
command=self.save_config
)
save_btn.pack(fill=tk.X, pady=5)
# 加载配置按钮
load_btn = tk.Button(
right_frame,
text="加载配置",
font=self.font,
bg='#FFB6C1',
command=self.load_config_ui
)
load_btn.pack(fill=tk.X, pady=5)
# 清空日志按钮
clear_btn = tk.Button(
right_frame,
text="清空日志",
font=self.font,
bg='#FFB6C1',
command=self.clear_log
)
clear_btn.pack(fill=tk.X, pady=5)
# 窗口置顶选项
topmost_check = tk.Checkbutton(
right_frame,
text="窗口置顶",
variable=self.topmost_var,
font=self.font,
bg='#FFFACD',
command=self.toggle_topmost
)
topmost_check.pack(pady=5)
# 日志区域
log_label = tk.Label(
right_frame,
text="执行日志",
font=(self.font[0], 14, 'bold'),
bg='#FFFACD'
)
log_label.pack(anchor=tk.W, pady=(10, 5))
self.log_text = scrolledtext.ScrolledText(
right_frame,
font=self.font,
bg='white',
height=25,
wrap=tk.WORD
)
self.log_text.pack(fill=tk.BOTH, expand=True)
# 进度条
self.progress = ttk.Progressbar(right_frame, mode='indeterminate')
self.progress.pack(fill=tk.X, pady=5)
# 状态栏
self.status_label = tk.Label(
self.root,
text="就绪",
font=self.font,
bg='#FFFACD',
relief=tk.SUNKEN,
anchor=tk.W
)
self.status_label.pack(side=tk.BOTTOM, fill=tk.X)
# 加载之前的配置
if hasattr(self, 'saved_config'):
self.load_saved_config()
def browse_script(self):
"""浏览Python脚本"""
filename = filedialog.askopenfilename(
title="选择Python脚本",
filetypes=[("Python文件", "*.py"), ("所有文件", "*.*")]
)
if filename:
self.script_path.delete(0, tk.END)
self.script_path.insert(0, filename)
# 自动设置输出名称
base_name = os.path.splitext(os.path.basename(filename))[0]
self.output_name.delete(0, tk.END)
self.output_name.insert(0, base_name)
def browse_output(self):
"""浏览输出目录"""
directory = filedialog.askdirectory(title="选择输出目录")
if directory:
self.output_dir.delete(0, tk.END)
self.output_dir.insert(0, directory)
def toggle_topmost(self):
"""切换窗口置顶"""
self.root.attributes('-topmost', self.topmost_var.get())
def start_packing(self):
"""开始打包"""
# 验证输入
script = self.script_path.get().strip()
if not script:
messagebox.showerror("错误", "请选择Python脚本")
return
if not os.path.exists(script):
messagebox.showerror("错误", f"脚本文件不存在: {script}")
return
output_dir = self.output_dir.get().strip()
if not output_dir:
messagebox.showerror("错误", "请选择输出目录")
return
# 在独立线程中执行打包
threading.Thread(target=self.pack_worker, daemon=True).start()
def pack_worker(self):
"""打包工作线程"""
try:
# 禁用按钮
self.root.after(0, lambda: self.progress.start())
self.root.after(0, lambda: self.status_label.config(text="正在打包..."))
self.log("=" * 60)
self.log("开始打包...")
# 构建命令
cmd = ["nuitka"]
# 添加选项
if self.options['standalone'].get():
cmd.append("--standalone")
self.log("启用 --standalone")
if self.options['follow_imports'].get():
cmd.append("--follow-imports")
self.log("启用 --follow-imports")
if self.options['remove_output'].get():
cmd.append("--remove-output")
self.log("启用 --remove-output")
if self.options['onefile'].get():
cmd.append("--onefile")
self.log("启用 --onefile")
if self.options['enable_plugin'].get():
cmd.append("--enable-plugin=tk-inter")
self.log("启用 tkinter 插件")
if self.options['show_progress'].get():
cmd.append("--show-progress")
self.log("启用进度显示")
# 重要:禁用静态Python库(解决Anaconda环境问题)
if self.options['static_libpython'].get():
cmd.append("--static-libpython=no")
self.log("禁用静态Python库")
if self.options['lto'].get():
cmd.append("--lto=no")
self.log("禁用链接时优化")
# 添加其他参数
other_args = self.other_args.get().strip()
if other_args:
cmd.extend(other_args.split())
self.log(f"其他参数: {other_args}")
# 输出目录
output_dir = self.output_dir.get().strip()
cmd.append(f"--output-dir={output_dir}")
# 输出文件名
output_name = self.output_name.get().strip()
if output_name:
cmd.append(f"--output-filename={output_name}")
# 脚本路径
cmd.append(self.script_path.get().strip())
self.log(f"\n执行命令: {' '.join(cmd)}\n")
# 执行命令
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1,
universal_newlines=True,
env=os.environ.copy()
)
# 实时输出
for line in process.stdout:
self.log(line.rstrip())
process.wait()
# 检查结果
if process.returncode == 0:
self.log("\n✓ 打包成功完成!")
output_file = os.path.join(output_dir, output_name)
if self.options['onefile'].get():
if not output_file.endswith('.bin'):
output_file += '.bin'
self.log(f"生成文件: {output_file}")
self.root.after(0, lambda: self.status_label.config(text="打包完成"))
self.root.after(0, lambda: messagebox.showinfo("成功", f"打包完成!\n输出文件: {output_file}"))
else:
self.log(f"\n✗ 打包失败,返回码: {process.returncode}")
self.root.after(0, lambda: self.status_label.config(text="打包失败"))
self.root.after(0, lambda: messagebox.showerror("错误", "打包失败,请查看日志"))
except Exception as e:
self.log(f"\n错误: {str(e)}")
self.root.after(0, lambda: self.status_label.config(text="发生错误"))
self.root.after(0, lambda: messagebox.showerror("错误", f"打包过程出错: {e}"))
finally:
self.root.after(0, lambda: self.progress.stop())
def log(self, message):
"""添加日志"""
def _log():
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
self.root.update_idletasks()
self.root.after(0, _log)
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
def save_config(self, event=None):
"""保存配置"""
try:
config = {
'script_path': self.script_path.get(),
'output_dir': self.output_dir.get(),
'output_name': self.output_name.get(),
'other_args': self.other_args.get(),
'options': {k: v.get() for k, v in self.options.items()}
}
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=2)
self.status_label.config(text="配置已保存")
except Exception as e:
messagebox.showerror("错误", f"保存配置失败: {e}")
def load_config(self):
"""加载配置"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
self.saved_config = json.load(f)
else:
self.saved_config = None
except:
self.saved_config = None
def load_config_ui(self):
"""从UI加载配置"""
if hasattr(self, 'saved_config') and self.saved_config:
self.load_saved_config()
messagebox.showinfo("成功", "配置已加载")
else:
messagebox.showwarning("警告", "没有找到保存的配置")
def load_saved_config(self):
"""加载保存的配置到UI"""
config = self.saved_config
if config:
self.script_path.delete(0, tk.END)
self.script_path.insert(0, config.get('script_path', ''))
self.output_dir.delete(0, tk.END)
self.output_dir.insert(0, config.get('output_dir', '/home/huanghe/test/xuanzhuan'))
self.output_name.delete(0, tk.END)
self.output_name.insert(0, config.get('output_name', '快捷地址'))
self.other_args.delete(0, tk.END)
self.other_args.insert(0, config.get('other_args', '--jobs=4'))
options = config.get('options', {})
for k, v in options.items():
if k in self.options:
self.options[k].set(v)
def quit_app(self, event=None):
"""退出程序"""
self.root.quit()
self.root.destroy()
def main():
# 检查nuitka是否安装
try:
result = subprocess.run(["nuitka", "--version"], capture_output=True, text=True)
if result.returncode != 0:
raise Exception("Nuitka not found")
except:
result = messagebox.askyesno("提示", "未检测到Nuitka,是否现在安装?\n\n安装命令: pip install nuitka")
if result:
os.system("pip install nuitka")
messagebox.showinfo("提示", "安装完成后请重新运行程序")
return
root = tk.Tk()
app = ElfPacker(root)
root.mainloop()
if __name__ == "__main__":
main()