vcpkg自动化安装库的界面程序

Vcpkg库自动安装器 - 使用说明

文章目录

程序概述

Vcpkg库自动安装器是一个图形化工具,用于批量安装和管理Vcpkg库。它提供了搜索、筛选、批量选择和管理功能,使您可以更轻松地管理和安装C/C++依赖库。

主要功能

  1. 库搜索与筛选​ - 通过关键词、分类筛选库

  2. 批量操作​ - 支持全选、全不选、反选库

  3. 可视化安装​ - 实时显示安装进度和日志

  4. 日志记录​ - 自动生成安装日志文件

  5. 多平台支持​ - 支持Windows、Linux、macOS

  6. 智能检测​ - 自动检测系统和Vcpkg路径

系统要求

  • Python 3.6 或更高版本

  • 已安装Tkinter库(通常Python自带)

  • 已安装vcpkg工具

  • Windows/Linux/macOS操作系统

快速开始

1. 安装依赖

复制代码
# 如果未安装Tkinter,在Ubuntu上可执行:
sudo apt-get install python3-tk

# 在CentOS上:
sudo yum install python3-tk

2. 运行程序

复制代码
python vcpkg_gui_installer_with_search.py

界面使用说明

主界面布局

界面分为以下几个主要区域:

  1. 顶部设置区

    • Vcpkg路径:选择vcpkg可执行文件位置

    • 库列表文件:包含要安装的库名称列表的文本文件

    • 目标三元组:选择编译目标平台

  2. 搜索筛选区

    • 搜索框:按库名搜索

    • 分类筛选:按功能分类筛选库

    • 排序选项:按名称或最近使用排序

  3. 库列表区

    • 显示所有可用的库

    • 支持多选操作

    • 显示库状态和分类

  4. 操作按钮区

    • 开始安装/停止安装

    • 保存选中库

  5. 进度和统计区

    • 显示安装进度条

    • 显示安装统计信息

  6. 日志区

    • 实时显示安装日志

    • 支持清除日志

详细使用步骤

步骤1:设置vcpkg路径
  1. 点击"浏览..."按钮选择vcpkg可执行文件

  2. 或让程序自动检测(程序启动时会尝试自动查找)

步骤2:加载库列表
  1. 准备一个文本文件,每行一个库名,例如:

    复制代码
    zlib
    boost
    openssl
    sdl2
    sfml
  2. 点击"浏览..."选择库列表文件

  3. 点击"加载库列表"加载库

步骤3:筛选和选择库
  1. 搜索库

    • 在搜索框中输入关键词,程序会自动过滤

    • 按回车或点击"搜索"按钮

  2. 分类筛选

    • 从下拉列表中选择库分类(图形、网络、数据库等)
  3. 选择库

    • 点击库名称可切换选中状态

    • 使用"全选"、"全不选"、"反选"按钮进行批量操作

    • 右键单击库可打开上下文菜单

步骤4:配置安装选项
  1. 目标三元组

    • 选择目标平台(x64-windows、x64-linux等)

    • 或选择"自动检测"让程序根据当前系统自动选择

  2. 安装选项

    • ✓ 安装静态库:安装静态链接版本

    • ✓ 强制重新安装:即使已安装也重新安装

步骤5:开始安装
  1. 点击"开始安装"按钮

  2. 确认安装信息

  3. 安装过程将在后台进行,可实时查看进度和日志

  4. 点击"停止安装"可中止安装过程

库列表文件格式

库列表文件是一个纯文本文件,支持以下格式:

基本格式

复制代码
zlib
boost
openssl

注释(以#开头)

复制代码
# 图形库
sdl2
sfml
opencv
# 网络库
libcurl

多行和分隔符

支持空格、逗号、分号分隔:

复制代码
zlib boost openssl
qt5, sdl2, glfw

右键菜单功能

在库列表中右键单击任何库,可执行以下操作:

  1. 搜索在线信息​ - 在浏览器中打开vcpkg网站搜索该库

  2. 查看依赖​ - 查看库的依赖关系(功能待实现)

  3. 复制库名​ - 复制库名称到剪贴板

目标三元组说明

目标三元组指定了库的编译目标,格式为:架构-平台[-链接方式]

Windows平台

  • x64-windows- 64位Windows,动态链接

  • x64-windows-static- 64位Windows,静态链接

  • x86-windows- 32位Windows,动态链接

  • x86-windows-static- 32位Windows,静态链接

  • arm64-windows- ARM64 Windows,动态链接

  • arm64-windows-static- ARM64 Windows,静态链接

Linux平台

  • x64-linux- 64位Linux,动态链接

  • x64-linux-static- 64位Linux,静态链接

macOS平台

  • x64-osx- 64位macOS,动态链接

  • x64-osx-static- 64位macOS,静态链接

  • arm64-osx- ARM64 macOS,动态链接

  • arm64-osx-static- ARM64 macOS,静态链接

常见问题

1. 程序找不到vcpkg怎么办?

  • 手动点击"浏览..."按钮选择vcpkg可执行文件

  • 确保vcpkg已正确安装并添加到系统PATH

2. 加载库列表时出错?

  • 检查文件路径是否正确

  • 确保文件格式正确(每行一个库名)

  • 检查文件编码是否为UTF-8

3. 安装过程中卡住?

  • 检查网络连接

  • 查看日志文件获取详细错误信息

  • 尝试单独安装有问题的库

4. 如何查看详细日志?

  • 程序界面显示实时日志

  • 完整的日志保存在程序运行目录的日志文件中

  • 日志文件名格式:vcpkg_install_YYYYMMDD_HHMMSS.log

注意事项

  1. 权限问题:在Linux/macOS上可能需要sudo权限才能安装到系统目录

  2. 网络连接:安装过程需要下载库源码,确保网络畅通

  3. 磁盘空间:确保有足够的磁盘空间安装库

  4. 时间:首次安装可能需要较长时间,取决于网络速度和库数量

  5. 兼容性:某些库可能不适用于所有平台和目标三元组

高级功能

自动检测目标三元组

选择"自动检测"时,程序会根据当前操作系统和架构自动选择合适的目标三元组。

静态/动态链接

  • 静态链接:库被链接到可执行文件中,生成的文件较大但不需要运行时库

  • 动态链接:库在运行时加载,生成的文件较小但需要目标系统有相应的运行时库

强制重新安装

启用此选项会强制重新安装所有选中的库,即使它们已经安装。

故障排除

安装失败常见原因:

  1. 网络连接问题

  2. 磁盘空间不足

  3. 权限不足

  4. 库名拼写错误

  5. 库不支持当前目标三元组

  6. 依赖冲突

解决方法:

  1. 检查网络连接

  2. 确保有足够的磁盘空间

  3. 以管理员/root权限运行程序

  4. 检查库名拼写

  5. 尝试不同的目标三元组

  6. 查看日志文件中的具体错误信息

技术支持

如果在使用过程中遇到问题:

  1. 查看程序生成的日志文件

  2. 检查vcpkg官方文档

  3. 确保系统和Python环境满足要求


注意:本程序是一个辅助工具,实际安装过程由vcpkg完成。请确保您了解vcpkg的基本使用方法。

程序主体

cpp 复制代码
# vcpkg_gui_installer_with_search.py
import subprocess
import sys
import os
import argparse
from datetime import datetime
import re
import platform
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext, simpledialog
import threading
import queue
import time
from typing import List, Tuple, Optional, Dict, Set
import webbrowser
from collections import defaultdict

class VcpkgInstallerGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Vcpkg库自动安装器 - 带搜索功能")
        self.root.geometry("1000x750")
        
        # 设置主题风格
        self.root.configure(bg="#f0f0f0")
        self.style = ttk.Style()
        self.style.theme_use('clam')
        
        # 安装线程控制
        self.install_thread = None
        self.stop_event = threading.Event()
        self.is_installing = False
        self.log_queue = queue.Queue()
        
        # 库列表相关
        self.all_libraries = []  # 所有库
        self.filtered_libraries = []  # 过滤后的库
        self.selected_libraries = set()  # 选中的库
        
        # 日志文件路径
        self.log_file = None
        
        # 创建界面
        self.create_widgets()
        
        # 启动日志更新循环
        self.update_log_display()
        
        # 尝试自动查找vcpkg
        self.auto_detect_vcpkg()
        
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def create_widgets(self):
        # 创建主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 第1行:标题
        title_label = ttk.Label(main_frame, 
                               text="Vcpkg库自动安装器 - 带搜索功能", 
                               font=("Arial", 16, "bold"),
                               foreground="#2c3e50")
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        
        # 第2行:vcpkg路径
        ttk.Label(main_frame, text="vcpkg路径:", 
                 font=("Arial", 10)).grid(row=1, column=0, sticky=tk.W, padx=(0, 5))
        
        self.vcpkg_path_var = tk.StringVar()
        vcpkg_entry = ttk.Entry(main_frame, textvariable=self.vcpkg_path_var, width=50)
        vcpkg_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=(0, 5))
        
        browse_vcpkg_btn = ttk.Button(main_frame, text="浏览...", 
                                     command=self.browse_vcpkg)
        browse_vcpkg_btn.grid(row=1, column=2, sticky=tk.W)
        
        # 第3行:库列表文件
        ttk.Label(main_frame, text="库列表文件:", 
                 font=("Arial", 10)).grid(row=2, column=0, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        self.library_file_var = tk.StringVar(value="C:/Code/vcpkg/libraries.txt")
        lib_file_entry = ttk.Entry(main_frame, textvariable=self.library_file_var, width=50)
        lib_file_entry.grid(row=2, column=1, sticky=(tk.W, tk.E), padx=(0, 5), pady=(10, 0))
        
        file_buttons_frame = ttk.Frame(main_frame)
        file_buttons_frame.grid(row=2, column=2, sticky=tk.W, pady=(10, 0))
        
        browse_lib_btn = ttk.Button(file_buttons_frame, text="浏览...", 
                                   command=self.browse_library_file)
        browse_lib_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.load_btn = ttk.Button(file_buttons_frame, text="加载库列表", 
                                   command=self.load_library_list)
        self.load_btn.pack(side=tk.LEFT)
        
        # 第4行:目标三元组
        ttk.Label(main_frame, text="目标三元组:", 
                 font=("Arial", 10)).grid(row=3, column=0, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        self.triplet_var = tk.StringVar()
        triplet_combo = ttk.Combobox(main_frame, textvariable=self.triplet_var, width=30)
        triplet_combo['values'] = (
            '自动检测',
            'x64-windows-static',
            'x64-windows',
            'x86-windows-static',
            'x86-windows',
            'arm64-windows-static',
            'arm64-windows',
            'x64-linux-static',
            'x64-linux',
            'x64-osx-static',
            'x64-osx',
            'arm64-osx-static',
            'arm64-osx'
        )
        triplet_combo.set('自动检测')
        triplet_combo.grid(row=3, column=1, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        # 第5行:搜索和筛选
        search_frame = ttk.LabelFrame(main_frame, text="搜索和筛选", padding="10")
        search_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(15, 10))
        search_frame.columnconfigure(0, weight=1)
        search_frame.columnconfigure(1, weight=1)
        search_frame.columnconfigure(2, weight=1)
        
        # 搜索框
        ttk.Label(search_frame, text="搜索:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5))
        
        self.search_var = tk.StringVar()
        self.search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
        self.search_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(0, 10))
        self.search_entry.bind('<KeyRelease>', self.on_search_changed)
        
        search_buttons_frame = ttk.Frame(search_frame)
        search_buttons_frame.grid(row=0, column=2, sticky=tk.E)
        
        search_btn = ttk.Button(search_buttons_frame, text="搜索", 
                               command=self.search_libraries, width=10)
        search_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        clear_search_btn = ttk.Button(search_buttons_frame, text="清除搜索", 
                                     command=self.clear_search, width=10)
        clear_search_btn.pack(side=tk.LEFT)
        
        # 高级筛选
        ttk.Label(search_frame, text="分类筛选:").grid(row=1, column=0, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        self.category_var = tk.StringVar(value="所有")
        category_combo = ttk.Combobox(search_frame, textvariable=self.category_var, 
                                     values=["所有", "图形", "网络", "数据库", "多媒体", "工具", "数学", "其他"], 
                                     width=15, state="readonly")
        category_combo.grid(row=1, column=1, sticky=tk.W, padx=(0, 10), pady=(10, 0))
        category_combo.bind('<<ComboboxSelected>>', self.on_category_changed)
        
        # 排序选项
        ttk.Label(search_frame, text="排序:").grid(row=1, column=2, sticky=tk.W, padx=(0, 5), pady=(10, 0))
        
        self.sort_var = tk.StringVar(value="名称")
        sort_combo = ttk.Combobox(search_frame, textvariable=self.sort_var, 
                                 values=["名称", "名称(降序)", "最近使用"], 
                                 width=15, state="readonly")
        sort_combo.grid(row=1, column=3, sticky=tk.W, pady=(10, 0))
        sort_combo.bind('<<ComboboxSelected>>', self.on_sort_changed)
        
        # 第6行:选项
        options_frame = ttk.LabelFrame(main_frame, text="安装选项", padding="10")
        options_frame.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 10))
        options_frame.columnconfigure(0, weight=1)
        options_frame.columnconfigure(1, weight=1)
        
        self.static_var = tk.BooleanVar(value=True)
        static_cb = ttk.Checkbutton(options_frame, text="安装静态库", 
                                    variable=self.static_var)
        static_cb.grid(row=0, column=0, sticky=tk.W, padx=(0, 20))
        
        self.force_var = tk.BooleanVar(value=False)
        force_cb = ttk.Checkbutton(options_frame, text="强制重新安装", 
                                   variable=self.force_var)
        force_cb.grid(row=0, column=1, sticky=tk.W)
        
        # 第7行:库列表显示框架
        lib_list_frame = ttk.LabelFrame(main_frame, text="库列表", padding="5")
        lib_list_frame.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        lib_list_frame.columnconfigure(0, weight=1)
        lib_list_frame.rowconfigure(0, weight=1)
        
        # 库列表控件框架
        lib_controls_frame = ttk.Frame(lib_list_frame)
        lib_controls_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        
        # 库列表操作按钮
        self.select_all_btn = ttk.Button(lib_controls_frame, text="全选", 
                                         command=self.select_all_libs, width=10)
        self.select_all_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.deselect_all_btn = ttk.Button(lib_controls_frame, text="全不选", 
                                          command=self.deselect_all_libs, width=10)
        self.deselect_all_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        self.invert_selection_btn = ttk.Button(lib_controls_frame, text="反选", 
                                              command=self.invert_selection, width=10)
        self.invert_selection_btn.pack(side=tk.LEFT, padx=(0, 5))
        
        # 库数量标签
        self.lib_count_label = ttk.Label(lib_controls_frame, text="库: 0/0 已选: 0")
        self.lib_count_label.pack(side=tk.LEFT, padx=(20, 0))
        
        # 库列表(使用Treeview)
        self.create_library_treeview(lib_list_frame)
        
        # 第8行:按钮
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=7, column=0, columnspan=3, pady=(5, 10))
        
        self.start_btn = ttk.Button(button_frame, text="开始安装", 
                                   command=self.start_installation,
                                   style="Accent.TButton")
        self.start_btn.pack(side=tk.LEFT, padx=(0, 10))
        
        self.stop_btn = ttk.Button(button_frame, text="停止安装", 
                                  command=self.stop_installation,
                                  state=tk.DISABLED)
        self.stop_btn.pack(side=tk.LEFT, padx=(0, 10))
        
        self.save_selected_btn = ttk.Button(button_frame, text="保存选中库", 
                                          command=self.save_selected_libraries)
        self.save_selected_btn.pack(side=tk.LEFT, padx=(0, 10))
        
        # 第9行:进度
        ttk.Label(main_frame, text="进度:", 
                 font=("Arial", 10)).grid(row=8, column=0, sticky=tk.W, padx=(0, 5), pady=(5, 0))
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(main_frame, length=300, 
                                           variable=self.progress_var, 
                                           mode='determinate')
        self.progress_bar.grid(row=8, column=1, columnspan=2, sticky=(tk.W, tk.E), 
                              pady=(5, 0), padx=(0, 0))
        
        # 进度标签
        self.progress_label = ttk.Label(main_frame, text="就绪")
        self.progress_label.grid(row=9, column=1, columnspan=2, sticky=tk.W, pady=(5, 10))
        
        # 第10行:统计信息
        stats_frame = ttk.Frame(main_frame)
        stats_frame.grid(row=10, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        
        self.total_var = tk.StringVar(value="总计: 0")
        self.installed_var = tk.StringVar(value="成功: 0")
        self.failed_var = tk.StringVar(value="失败: 0")
        self.skipped_var = tk.StringVar(value="跳过: 0")
        self.selected_count_var = tk.StringVar(value="已选: 0")
        
        ttk.Label(stats_frame, textvariable=self.total_var, 
                 font=("Arial", 9)).pack(side=tk.LEFT, padx=(0, 15))
        ttk.Label(stats_frame, textvariable=self.selected_count_var, 
                 font=("Arial", 9), foreground="blue").pack(side=tk.LEFT, padx=(0, 15))
        ttk.Label(stats_frame, textvariable=self.installed_var, 
                 font=("Arial", 9), foreground="green").pack(side=tk.LEFT, padx=(0, 15))
        ttk.Label(stats_frame, textvariable=self.failed_var, 
                 font=("Arial", 9), foreground="red").pack(side=tk.LEFT, padx=(0, 15))
        ttk.Label(stats_frame, textvariable=self.skipped_var, 
                 font=("Arial", 9), foreground="orange").pack(side=tk.LEFT)
        
        # 第11行:日志标签
        ttk.Label(main_frame, text="安装日志:", 
                 font=("Arial", 10)).grid(row=11, column=0, sticky=tk.NW, padx=(0, 5), pady=(5, 0))
        
        # 日志文本框
        self.log_text = scrolledtext.ScrolledText(main_frame, height=12, width=80, 
                                                 font=("Consolas", 9))
        self.log_text.grid(row=12, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), 
                          pady=(5, 0))
        
        # 第12行:日志按钮
        log_buttons_frame = ttk.Frame(main_frame)
        log_buttons_frame.grid(row=13, column=0, columnspan=3, sticky=tk.E, pady=(5, 0))
        
        clear_log_btn = ttk.Button(log_buttons_frame, text="清除日志", 
                                  command=self.clear_log)
        clear_log_btn.pack(side=tk.RIGHT, padx=(5, 0))
        
        # 配置网格权重
        main_frame.rowconfigure(6, weight=1)
        main_frame.rowconfigure(12, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 创建自定义样式
        self.style.configure('Accent.TButton', foreground='white', background='#3498db')
        self.style.map('Accent.TButton',
                      background=[('active', '#2980b9')])

    def create_library_treeview(self, parent):
        """创建库列表Treeview"""
        # 创建Treeview
        self.tree_frame = ttk.Frame(parent)
        self.tree_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 0))
        
        # 配置网格权重
        parent.rowconfigure(1, weight=1)
        parent.columnconfigure(0, weight=1)
        self.tree_frame.columnconfigure(0, weight=1)
        self.tree_frame.rowconfigure(0, weight=1)
        
        # 创建Treeview
        self.tree = ttk.Treeview(self.tree_frame, columns=("name", "category", "status"), 
                                show="tree headings", height=10)
        
        # 设置列
        self.tree.heading("#0", text="", anchor=tk.W)
        self.tree.heading("name", text="库名称", anchor=tk.W)
        self.tree.heading("category", text="分类", anchor=tk.W)
        self.tree.heading("status", text="状态", anchor=tk.W)
        
        # 设置列宽
        self.tree.column("#0", width=0, stretch=False)
        self.tree.column("name", width=300, minwidth=200)
        self.tree.column("category", width=100, minwidth=80)
        self.tree.column("status", width=100, minwidth=80)
        
        # 添加滚动条
        tree_scroll = ttk.Scrollbar(self.tree_frame, orient="vertical", command=self.tree.yview)
        self.tree.configure(yscrollcommand=tree_scroll.set)
        
        # 布局
        self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        tree_scroll.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        # 绑定事件
        self.tree.bind('<ButtonRelease-1>', self.on_tree_select)
        self.tree.bind('<Double-Button-1>', self.on_tree_double_click)
        
        # 添加右键菜单
        self.setup_tree_context_menu()

    def setup_tree_context_menu(self):
        """设置Treeview的右键菜单"""
        self.tree_menu = tk.Menu(self.root, tearoff=0)
        self.tree_menu.add_command(label="全选", command=self.select_all_libs)
        self.tree_menu.add_command(label="全不选", command=self.deselect_all_libs)
        self.tree_menu.add_separator()
        self.tree_menu.add_command(label="搜索在线信息", command=self.search_online_info)
        self.tree_menu.add_command(label="查看依赖", command=self.show_dependencies)
        self.tree_menu.add_separator()
        self.tree_menu.add_command(label="复制库名", command=self.copy_library_name)
        
        # 绑定右键点击事件
        self.tree.bind('<Button-3>', self.show_tree_context_menu)

    def show_tree_context_menu(self, event):
        """显示Treeview的右键菜单"""
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.tree_menu.post(event.x_root, event.y_root)

    def auto_detect_vcpkg(self):
        """自动检测vcpkg路径"""
        try:
            vcpkg_path = self.find_vcpkg()
            self.vcpkg_path_var.set(vcpkg_path)
            self.log(f"自动检测到vcpkg: {vcpkg_path}")
        except Exception as e:
            self.log(f"警告: 未找到vcpkg: {str(e)}")
            self.log("请手动选择vcpkg路径")

    def find_vcpkg(self):
        """定位vcpkg可执行文件"""
        if os.name == 'nt':
            result = subprocess.run(["where", "vcpkg"], 
                                  capture_output=True, text=True, shell=True)
        else:
            result = subprocess.run(["which", "vcpkg"], 
                                  capture_output=True, text=True)
        
        if result.returncode == 0:
            paths = [p.strip() for p in result.stdout.strip().splitlines() if p.strip()]
            if paths:
                return paths[0]
        
        # 检查常见的安装位置
        common_paths = []
        
        if os.name == 'nt':  # Windows
            common_paths = [
                os.path.expanduser("~/vcpkg/vcpkg.exe"),
                "C:/vcpkg/vcpkg.exe",
                os.path.join(os.getcwd(), "vcpkg.exe"),
            ]
        else:  # Linux/macOS
            common_paths = [
                os.path.expanduser("~/vcpkg/vcpkg"),
                "/usr/local/bin/vcpkg",
                os.path.join(os.getcwd(), "vcpkg"),
            ]
        
        for path in common_paths:
            if os.path.exists(path):
                return os.path.abspath(path)
        
        raise Exception("未找到vcpkg!")

    def browse_vcpkg(self):
        """浏览选择vcpkg可执行文件"""
        if os.name == 'nt':
            filetypes = [("可执行文件", "*.exe"), ("所有文件", "*.*")]
        else:
            filetypes = [("可执行文件", "*"), ("所有文件", "*.*")]
        
        filename = filedialog.askopenfilename(
            title="选择vcpkg可执行文件",
            filetypes=filetypes
        )
        if filename:
            self.vcpkg_path_var.set(filename)
            self.log(f"已选择vcpkg: {filename}")

    def browse_library_file(self):
        """浏览选择库列表文件"""
        filename = filedialog.askopenfilename(
            title="选择库列表文件",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        if filename:
            self.library_file_var.set(filename)
            self.log(f"已选择库列表文件: {filename}")
            # 自动加载库列表
            self.load_library_list()

    def load_library_list(self):
        """从文件加载库列表"""
        filename = self.library_file_var.get()
        if not filename or not os.path.exists(filename):
            messagebox.showerror("错误", f"库列表文件 '{filename}' 不存在!")
            return
        
        try:
            libraries = self.read_libraries_from_file(filename)
            if not libraries:
                messagebox.showwarning("警告", f"库列表文件 '{filename}' 为空或没有有效的库名!")
                return
            
            # 保存所有库
            self.all_libraries = libraries
            self.filtered_libraries = libraries.copy()
            
            # 更新Treeview
            self.update_library_treeview()
            
            self.log(f"从 '{filename}' 加载了 {len(libraries)} 个库")
            self.update_stats(total=len(libraries))
            
        except Exception as e:
            messagebox.showerror("错误", f"读取库列表文件失败: {str(e)}")

    def read_libraries_from_file(self, filename):
        """从文本文件读取库列表"""
        libraries = []
        
        with open(filename, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                if "#" in line:
                    line = line.split("#")[0].strip()
                
                if any(sep in line for sep in [" ", ",", ";"]):
                    for lib in re.split(r'[ ,;]+', line):
                        if lib:
                            libraries.append(lib.strip())
                else:
                    libraries.append(line)
        
        return libraries

    def update_library_treeview(self):
        """更新库列表Treeview"""
        # 清空Treeview
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # 添加库到Treeview
        for lib in self.filtered_libraries:
            # 确定库的分类
            category = self.guess_library_category(lib)
            
            # 确定状态
            status = "已选中" if lib in self.selected_libraries else "未选中"
            
            # 添加项目
            item_id = self.tree.insert("", tk.END, values=(lib, category, status))
            
            # 设置颜色
            if lib in self.selected_libraries:
                self.tree.item(item_id, tags=("selected",))
            else:
                self.tree.item(item_id, tags=("unselected",))
        
        # 配置标签样式
        self.tree.tag_configure("selected", background="#e1f5fe")
        self.tree.tag_configure("unselected", background="white")
        
        # 更新库数量
        self.update_library_count()

    def guess_library_category(self, library_name):
        """根据库名猜测分类"""
        library_name = library_name.lower()
        
        # 图形相关
        graphics_keywords = ["opengl", "glfw", "glew", "glm", "sfml", "sdl", "qt", "wx", 
                           "gtk", "cairo", "opencv", "image", "graphic", "gui"]
        if any(keyword in library_name for keyword in graphics_keywords):
            return "图形"
        
        # 网络相关
        network_keywords = ["curl", "asio", "boost-asio", "mongoose", "http", "websocket", 
                          "mqtt", "grpc", "rest", "socket", "network"]
        if any(keyword in library_name for keyword in network_keywords):
            return "网络"
        
        # 数据库相关
        db_keywords = ["sql", "mysql", "postgres", "sqlite", "mongo", "redis", "database", 
                      "odbc", "jdbc"]
        if any(keyword in library_name for keyword in db_keywords):
            return "数据库"
        
        # 多媒体相关
        media_keywords = ["ffmpeg", "gstreamer", "openal", "portaudio", "audio", "video", 
                         "sound", "music", "vlc"]
        if any(keyword in library_name for keyword in media_keywords):
            return "多媒体"
        
        # 工具相关
        tool_keywords = ["boost", "fmt", "spdlog", "gtest", "catch2", "benchmark", "yaml", 
                        "json", "xml", "toml", "ini", "log", "test"]
        if any(keyword in library_name for keyword in tool_keywords):
            return "工具"
        
        # 数学相关
        math_keywords = ["eigen", "armadillo", "blas", "lapack", "openblas", "gsl", "math", 
                        "numerical", "linear", "algebra"]
        if any(keyword in library_name for keyword in math_keywords):
            return "数学"
        
        return "其他"

    def on_search_changed(self, event=None):
        """搜索框内容变化时的处理"""
        # 在用户输入后自动搜索
        self.root.after(500, self.search_libraries)

    def search_libraries(self):
        """搜索库"""
        search_term = self.search_var.get().strip().lower()
        category = self.category_var.get()
        sort_by = self.sort_var.get()
        
        if not search_term and category == "所有":
            # 如果没有搜索条件,显示所有库
            self.filtered_libraries = self.all_libraries.copy()
        else:
            # 根据搜索条件过滤
            self.filtered_libraries = []
            for lib in self.all_libraries:
                lib_lower = lib.lower()
                lib_category = self.guess_library_category(lib)
                
                # 检查搜索条件
                matches_search = not search_term or search_term in lib_lower
                matches_category = category == "所有" or lib_category == category
                
                if matches_search and matches_category:
                    self.filtered_libraries.append(lib)
        
        # 排序
        if sort_by == "名称":
            self.filtered_libraries.sort()
        elif sort_by == "名称(降序)":
            self.filtered_libraries.sort(reverse=True)
        elif sort_by == "最近使用":
            # 这里可以添加最近使用的逻辑
            self.filtered_libraries.sort()
        
        # 更新Treeview
        self.update_library_treeview()
        
        # 显示搜索结果
        if search_term:
            self.log(f"搜索 '{search_term}' 找到 {len(self.filtered_libraries)} 个库")
        else:
            self.log(f"显示 {len(self.filtered_libraries)} 个库")

    def clear_search(self):
        """清除搜索"""
        self.search_var.set("")
        self.category_var.set("所有")
        self.sort_var.set("名称")
        self.search_libraries()

    def on_category_changed(self, event=None):
        """分类筛选变化时的处理"""
        self.search_libraries()

    def on_sort_changed(self, event=None):
        """排序选项变化时的处理"""
        self.search_libraries()

    def on_tree_select(self, event):
        """Treeview选择事件处理"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            values = self.tree.item(item, "values")
            if values:
                library_name = values[0]
                
                # 切换选中状态
                if library_name in self.selected_libraries:
                    self.selected_libraries.remove(library_name)
                    self.tree.item(item, tags=("unselected",))
                    self.tree.set(item, "status", "未选中")
                else:
                    self.selected_libraries.add(library_name)
                    self.tree.item(item, tags=("selected",))
                    self.tree.set(item, "status", "已选中")
                
                # 更新库数量
                self.update_library_count()

    def on_tree_double_click(self, event):
        """Treeview双击事件处理"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            values = self.tree.item(item, "values")
            if values:
                library_name = values[0]
                # 显示库详情
                self.show_library_details(library_name)

    def select_all_libs(self):
        """全选所有库"""
        for lib in self.filtered_libraries:
            self.selected_libraries.add(lib)
        self.update_library_treeview()

    def deselect_all_libs(self):
        """取消全选"""
        self.selected_libraries.clear()
        self.update_library_treeview()

    def invert_selection(self):
        """反选"""
        for lib in self.filtered_libraries:
            if lib in self.selected_libraries:
                self.selected_libraries.remove(lib)
            else:
                self.selected_libraries.add(lib)
        self.update_library_treeview()

    def update_library_count(self):
        """更新库数量显示"""
        total_count = len(self.filtered_libraries)
        selected_count = len([lib for lib in self.filtered_libraries if lib in self.selected_libraries])
        self.lib_count_label.config(text=f"库: {selected_count}/{total_count}")
        self.selected_count_var.set(f"已选: {selected_count}")

    def show_library_details(self, library_name):
        """显示库详情"""
        # 这里可以添加显示库详情的逻辑
        # 例如,从vcpkg获取库的描述信息
        messagebox.showinfo("库详情", f"库: {library_name}\n\n详细信息功能待实现")

    def search_online_info(self):
        """搜索在线信息"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            values = self.tree.item(item, "values")
            if values:
                library_name = values[0]
                # 在浏览器中搜索库信息
                search_url = f"https://vcpkg.io/packages/search?q={library_name}"
                webbrowser.open(search_url)
                self.log(f"在浏览器中打开: {search_url}")

    def show_dependencies(self):
        """显示依赖"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            values = self.tree.item(item, "values")
            if values:
                library_name = values[0]
                # 这里可以添加获取依赖的逻辑
                messagebox.showinfo("依赖信息", f"库: {library_name}\n\n依赖查询功能待实现")

    def copy_library_name(self):
        """复制库名"""
        selection = self.tree.selection()
        if selection:
            item = selection[0]
            values = self.tree.item(item, "values")
            if values:
                library_name = values[0]
                self.root.clipboard_clear()
                self.root.clipboard_append(library_name)
                self.log(f"已复制库名: {library_name}")

    def save_selected_libraries(self):
        """保存选中的库到文件"""
        if not self.selected_libraries:
            messagebox.showwarning("警告", "没有选中的库!")
            return
        
        filename = filedialog.asksaveasfilename(
            title="保存选中的库",
            defaultextension=".txt",
            filetypes=[("文本文件", "*.txt"), ("所有文件", "*.*")]
        )
        
        if filename:
            try:
                with open(filename, "w", encoding="utf-8") as f:
                    for lib in sorted(self.selected_libraries):
                        f.write(f"{lib}\n")
                self.log(f"已保存 {len(self.selected_libraries)} 个库到: {filename}")
                messagebox.showinfo("成功", f"已保存 {len(self.selected_libraries)} 个库到文件")
            except Exception as e:
                messagebox.showerror("错误", f"保存文件失败: {str(e)}")

    def determine_triplet(self):
        """确定目标三元组"""
        selected = self.triplet_var.get()
        if selected != "自动检测":
            return selected
        
        # 自动检测逻辑
        system = platform.system().lower()
        machine = platform.machine().lower()
        
        if system == "windows":
            if machine in ["amd64", "x86_64"]:
                arch = "x64"
            elif machine in ["i386", "i686", "x86"]:
                arch = "x86"
            elif machine in ["arm64", "aarch64"]:
                arch = "arm64"
            else:
                arch = "x64"
            
            return f"{arch}-windows-static" if self.static_var.get() else f"{arch}-windows"
        
        elif system == "linux":
            if machine in ["x86_64", "amd64"]:
                arch = "x64"
            elif machine in ["i386", "i686", "x86"]:
                arch = "x86"
            elif machine in ["arm64", "aarch64"]:
                arch = "arm64"
            else:
                arch = "x64"
            
            return f"{arch}-linux-static" if self.static_var.get() else f"{arch}-linux"
        
        elif system == "darwin":
            if machine in ["x86_64", "amd64"]:
                arch = "x64"
            elif machine in ["arm64", "aarch64"]:
                arch = "arm64"
            else:
                arch = "x64"
            
            return f"{arch}-osx-static" if self.static_var.get() else f"{arch}-osx"
        
        else:
            return "x64-windows-static" if self.static_var.get() else "x64-windows"

    def start_installation(self):
        """开始安装"""
        # 验证输入
        vcpkg_path = self.vcpkg_path_var.get().strip()
        if not vcpkg_path or not os.path.exists(vcpkg_path):
            messagebox.showerror("错误", "请选择有效的vcpkg路径!")
            return
        
        # 获取选中的库
        if not self.selected_libraries:
            messagebox.showerror("错误", "没有选中的库! 请在列表中选中要安装的库")
            return
        
        libraries = list(self.selected_libraries)
        
        # 获取三元组
        triplet = self.determine_triplet()
        
        # 确认
        confirm = messagebox.askyesno(
            "确认安装",
            f"是否要安装 {len(libraries)} 个库?\n"
            f"目标三元组: {triplet}\n"
            f"vcpkg路径: {vcpkg_path}"
        )
        if not confirm:
            return
        
        # 设置日志文件
        self.log_file = f"vcpkg_install_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
        with open(self.log_file, "w", encoding="utf-8") as f:
            f.write(f"=== 自动安装开始 ({datetime.now()}) ===\n")
            f.write(f"vcpkg路径: {vcpkg_path}\n")
            f.write(f"目标三元组: {triplet}\n")
            f.write(f"库数量: {len(libraries)}\n")
            f.write("=" * 50 + "\n\n")
        
        # 重置状态
        self.stop_event.clear()
        self.is_installing = True
        self.start_btn.config(state=tk.DISABLED)
        self.stop_btn.config(state=tk.NORMAL)
        self.load_btn.config(state=tk.DISABLED)
        self.save_selected_btn.config(state=tk.DISABLED)
        
        # 重置进度
        self.progress_var.set(0)
        self.update_stats(installed=0, failed=0, skipped=0)
        
        # 启动安装线程
        self.install_thread = threading.Thread(
            target=self.install_thread_func,
            args=(vcpkg_path, libraries, triplet),
            daemon=True
        )
        self.install_thread.start()

    def install_thread_func(self, vcpkg_path: str, libraries: List[str], triplet: str):
        """安装线程函数"""
        installed_count = 0
        failed_count = 0
        skipped_count = 0
        failed_libs = []
        
        self.log(f"开始安装 {len(libraries)} 个库...")
        self.log(f"目标三元组: {triplet}")
        self.log(f"vcpkg路径: {vcpkg_path}")
        self.log("-" * 50)
        
        for i, lib in enumerate(libraries, 1):
            if self.stop_event.is_set():
                self.log("安装被用户停止")
                break
            
            # 更新进度
            progress = (i - 1) / len(libraries) * 100
            self.progress_var.set(progress)
            self.progress_label.config(text=f"正在安装: {lib} ({i}/{len(libraries)})")
            
            self.log(f"[{i}/{len(libraries)}] 安装: {lib}")
            
            # 检查是否已安装
            if not self.force_var.get() and self.is_library_installed(vcpkg_path, lib, triplet):
                self.log(f"  -> 已安装,跳过")
                skipped_count += 1
                self.update_stats(skipped=skipped_count)
                continue
            
            # 安装库
            success = self.install_library(vcpkg_path, lib, triplet, i, len(libraries))
            
            if success:
                self.log(f"  -> 安装成功")
                installed_count += 1
                self.update_stats(installed=installed_count)
            else:
                self.log(f"  -> 安装失败")
                failed_count += 1
                failed_libs.append(lib)
                self.update_stats(failed=failed_count)
        
        # 安装完成
        self.is_installing = False
        self.progress_var.set(100)
        
        # 更新UI
        self.root.after(0, self.on_installation_complete, 
                       len(libraries), installed_count, skipped_count, 
                       failed_count, failed_libs)

    def is_library_installed(self, vcpkg_path: str, library: str, triplet: str) -> bool:
        """检查库是否已安装"""
        try:
            result = subprocess.run([vcpkg_path, "list"], 
                                  capture_output=True, text=True, shell=True)
            
            pattern = re.compile(rf"^{library}:[\w-]+\s+[\d\.]+\s+{triplet}")
            for line in result.stdout.splitlines():
                if pattern.match(line.strip()):
                    return True
            return False
        except:
            return False

    def install_library(self, vcpkg_path: str, library: str, triplet: str, 
                        current: int, total: int) -> bool:
        """安装单个库"""
        try:
            with open(self.log_file, "a", encoding="utf-8") as f:
                f.write(f"\n--- [{current}/{total}] 安装 {library} ---\n")
                
                process = subprocess.run(
                    [vcpkg_path, "install", f"{library}:{triplet}", "--recurse"],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.STDOUT,
                    text=True,
                    encoding="utf-8",
                    errors="replace"
                )
                
                # 将输出记录到日志文件和控制台
                f.write(process.stdout)
                f.write(f"\n返回码: {process.returncode}\n")
                
                # 在GUI中显示输出
                self.log_queue.put(process.stdout[-2000:])  # 只显示最后2000字符
                
            return process.returncode == 0
        except Exception as e:
            error_msg = f"安装错误: {str(e)}"
            with open(self.log_file, "a", encoding="utf-8") as f:
                f.write(error_msg + "\n")
            self.log_queue.put(error_msg)
            return False

    def on_installation_complete(self, total: int, installed: int, 
                                skipped: int, failed: int, failed_libs: List[str]):
        """安装完成回调"""
        self.start_btn.config(state=tk.NORMAL)
        self.stop_btn.config(state=tk.DISABLED)
        self.load_btn.config(state=tk.NORMAL)
        self.save_selected_btn.config(state=tk.NORMAL)
        
        self.progress_label.config(text="安装完成!")
        
        # 显示摘要
        self.log("\n" + "="*50)
        self.log("安装完成!")
        self.log(f"总计: {total} 个库")
        self.log(f"成功安装: {installed}")
        self.log(f"已跳过: {skipped}")
        self.log(f"安装失败: {failed}")
        
        if self.log_file:
            self.log(f"详细日志: {self.log_file}")
        
        if failed_libs:
            self.log(f"\n失败的库 ({len(failed_libs)} 个):")
            for lib in failed_libs:
                self.log(f"  - {lib}")
        
        # 弹出完成对话框
        message = f"安装完成!\n\n总计: {total} 个库\n成功: {installed}\n跳过: {skipped}\n失败: {failed}"
        if failed > 0:
            messagebox.showwarning("安装完成", message)
        else:
            messagebox.showinfo("安装完成", message)

    def stop_installation(self):
        """停止安装"""
        if self.is_installing:
            self.stop_event.set()
            self.log("正在停止安装...")
            self.progress_label.config(text="正在停止...")

    def update_stats(self, total=None, installed=None, failed=None, skipped=None, selected=None):
        """更新统计信息"""
        if total is not None:
            self.total_var.set(f"总计: {total}")
        if installed is not None:
            self.installed_var.set(f"成功: {installed}")
        if failed is not None:
            self.failed_var.set(f"失败: {failed}")
        if skipped is not None:
            self.skipped_var.set(f"跳过: {skipped}")

    def log(self, message: str):
        """添加日志消息到队列"""
        self.log_queue.put(message)

    def update_log_display(self):
        """更新日志显示"""
        try:
            while True:
                message = self.log_queue.get_nowait()
                timestamp = datetime.now().strftime("%H:%M:%S")
                self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
                self.log_text.see(tk.END)
        except queue.Empty:
            pass
        
        # 每隔100ms检查一次
        self.root.after(100, self.update_log_display)

    def clear_log(self):
        """清除日志"""
        self.log_text.delete(1.0, tk.END)
        self.log("日志已清除")

    def on_closing(self):
        """窗口关闭事件处理"""
        if self.is_installing:
            if messagebox.askyesno("确认", "安装正在进行中,确定要退出吗?"):
                self.stop_event.set()
                self.root.destroy()
        else:
            self.root.destroy()

def main():
    root = tk.Tk()
    app = VcpkgInstallerGUI(root)
    root.mainloop()

if __name__ == "__main__":
    main()
相关推荐
编程研究坊2 小时前
LabelStudio linux 系统下部署教程
linux·运维·服务器
ybdesire2 小时前
在CentOS 7安装配置CodeQL与运行QL扫描
linux·运维·centos
waves浪游2 小时前
进程控制(下)
linux·运维·服务器·开发语言·c++
ai_xiaogui2 小时前
Debian系统PVE虚拟机安装详解:ISO镜像上传+硬件配置+图形化安装指南
运维·debian·php·panelai兼容测试·图形化安装指南·iso镜像上传配置·debian pve虚拟机安装
周周记笔记2 小时前
Ubuntu 中如何配置域名
运维·服务器
KYGALYX2 小时前
Win10/11系统下WSL2+Ubuntu的全流程安装
linux·运维·ubuntu
Yyyy48210 小时前
Ubuntu安装Jenkis
linux·运维·ubuntu
克莱斯勒ya11 小时前
服务器硬件配置
运维·服务器
GTgiantech11 小时前
精准成本控制与单向通信优化:1X9、SFP单收/单发光模块专业解析
运维·网络