python制作的软件工具安装包

思考:

  • 如何将制作的软件工具分发给用户?
  • 打成压缩包分发给用户;
  • 制作安装程序
  • 本文介绍了一种将软件工具打包为独立安装程序的方法。核心思路是将目录文件转换为Base64编码的JSON文件,再通过解码程序还原为可执行文件。

内容说明

主要包含两个Python脚本:directory_to_base64.py将指定目录转换为包含文件内容的JSON文件;base64_to_directory.py则执行反向操作,将JSON还原为目录结构。文章详细说明了使用PyInstaller打包时的注意事项,包括资源文件路径处理、版本信息配置等,并提供了完整的示例代码和spec文件配置模板。这种方法适用于需要将多个文件打包为单一可执行安装程序的场景。

核心代码

python 复制代码
'''directory_to_base64.py'''

import os
import base64
import json
import argparse
from pathlib import Path

def file_to_base64(file_path):
    """将文件内容转换为 Base64 编码"""
    try:
        with open(file_path, 'rb') as file:
            content = file.read()
            return base64.b64encode(content).decode('utf-8')
    except Exception as e:
        print(f"Error reading file {file_path}: {e}")
        return None

def directory_to_dict(dir_path):
    """递归将目录结构转换为字典"""
    result = {}
    for item in os.listdir(dir_path):
        item_path = os.path.join(dir_path, item)
        if os.path.isfile(item_path):
            base64_content = file_to_base64(item_path)
            if base64_content is not None:
                result[item] = {
                    'type': 'file',
                    'content': base64_content
                }
        elif os.path.isdir(item_path):
            result[item] = {
                'type': 'directory',
                'content': directory_to_dict(item_path)
            }
    return result

def main():
    parser = argparse.ArgumentParser(description='Convert a directory and its contents to Base64 encoded JSON')
    parser.add_argument('--input', '-i', required=True, help='Input directory path')
    parser.add_argument('--output', '-o', required=True, help='Output JSON file path')
    args = parser.parse_args()

    input_dir = args.input
    output_file = args.output

    if not os.path.isdir(input_dir):
        print(f"Error: Input directory '{input_dir}' does not exist or is not a directory.")
        return

    try:
        # 创建输出文件的目录(如果不存在)
        output_dir = os.path.dirname(output_file)
        if output_dir:
            os.makedirs(output_dir, exist_ok=True)

        # 转换目录结构为字典
        dir_dict = directory_to_dict(input_dir)

        # 添加元数据
        metadata = {
            'root_directory': os.path.basename(os.path.abspath(input_dir)),
            'total_files': sum(1 for _ in Path(input_dir).rglob('*') if os.path.isfile(_)),
            'total_directories': sum(1 for _ in Path(input_dir).rglob('*') if os.path.isdir(_))
        }

        result = {
            'metadata': metadata,
            'content': dir_dict
        }

        # 写入 JSON 文件
        with open(output_file, 'w') as f:
            json.dump(result, f, indent=2)

        print(f"Successfully converted directory '{input_dir}' to Base64 encoded JSON in '{output_file}'.")

    except Exception as e:
        print(f"An error occurred: {e}")

if __name__ == "__main__":
    main()    
python 复制代码
'''base64_to_directory.py'''

import os
import base64
import json
import argparse
import sys
import pyexpat

from pyexpat.errors import messages


def decode_base64_to_file(base64_content, output_path):
    """将 Base64 编码内容写入文件"""
    try:
        with open(output_path, 'wb') as file:
            file.write(base64.b64decode(base64_content))
        return True
    except Exception as e:
        print(f"Error writing file {output_path}: {e}")
        return False


def process_directory(content_dict, output_dir):
    """递归处理目录结构并还原文件"""
    for item_name, item_data in content_dict.items():
        item_path = os.path.join(output_dir, item_name)

        if item_data['type'] == 'file':
            # 创建文件
            base64_content = item_data['content']
            if decode_base64_to_file(base64_content, item_path):
                print(f"Created file: {item_path}")
        elif item_data['type'] == 'directory':
            # 创建目录
            os.makedirs(item_path, exist_ok=True)
            print(f"Created directory: {item_path}")
            # 递归处理子目录
            process_directory(item_data['content'], item_path)

def resource_path(relative_path):
    """获取资源的绝对路径,适应打包后的环境"""
    if hasattr(sys, '_MEIPASS'):
        # 打包后的环境
        return os.path.join(sys._MEIPASS, relative_path)
    # 开发环境
    return os.path.join(os.path.abspath("."), relative_path)

def main():
    """    parser = argparse.ArgumentParser(description='Decode a Base64 encoded JSON file back to directory structure')
    parser.add_argument('--input', '-i', required=True, help='Input JSON file path')
    parser.add_argument('--output', '-o', required=True, help='Output directory path')
    args = parser.parse_args()"""

    # 使用示例
    input_file = resource_path("input.json")
    output_dir = "./Tool"

    """if not os.path.isfile(input_file):
        print(f"Error: Input file '{input_file}' does not exist or is not a file.")
        return
"""
    try:
        # 读取 JSON 文件
        with open(input_file, 'r') as f:
            data = json.load(f)

        # 创建输出目录(如果不存在)
        os.makedirs(output_dir, exist_ok=True)

        # 获取元数据
        metadata = data.get('metadata', {})
        root_directory = metadata.get('root_directory', '')
        total_files = metadata.get('total_files', 0)
        total_dirs = metadata.get('total_directories', 0)

        print(f"Decoding directory: {root_directory}")
        print(f"Total files expected: {total_files}")
        print(f"Total directories expected: {total_dirs}")
        print(f"Output location: {output_dir}")
        print("Starting decoding process...")

        # 处理内容
        content_dict = data.get('content', {})
        process_directory(content_dict, output_dir)

        print("Decoding completed successfully!")

    except Exception as e:
        print(f"An error occurred: {e}")


if __name__ == "__main__":
    main()
XML 复制代码
#base64_to_directory.spec

# -*- mode: python ; coding: utf-8 -*-

a = Analysis(
    ['D:\\workSpace\\python_work\\learn\\STtest\\build\\base64_to_directory.py'],
    pathex=[],
    binaries=[],
    datas=[('input.json', '.')],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='csvfileBatchGenerationToolInstall',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    version='version.txt'     # 指定版本信息文件
)
XML 复制代码
# version.txt
# 版本信息文件
VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=(1, 0, 0, 0),
    prodvers=(1, 0, 0, 0),
    mask=0x3f,
    flags=0x0,
    OS=0x40004,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
  ),
  kids=[
    StringFileInfo(
      [
        StringTable(
          '080404B0',
          [
            StringStruct('CompanyName', ''),
            StringStruct('FileDescription','工具安装程序'),
            StringStruct('FileVersion', '1.0.0'),
            StringStruct('InternalName', 'csv_data_install'),
            StringStruct('LegalCopyright', '© 2025 Company. All rights reserved.'),
            StringStruct('OriginalFilename', 'install.exe'),
            StringStruct('ProductName', 'ToolInstall'),
            StringStruct('ProductVersion', '1.0.0')
          ]
        )
      ]
    ),
    VarFileInfo(
      [
        VarStruct('Translation', [2052, 1200])  # 2052=中文
      ]
    )
  ]
)

注意事项

input.json文件作为数据资源文件,需要通过os.path.join(sys._MEIPASS, relative_path)设定打包后的路径。datas=[('input.json', '.')]指定资源文件('原路径','目标路径')。执行命令pyinstaller "spec文件路径"打包。

相关推荐
闲人编程4 分钟前
Python第三方库IPFS-API使用详解:构建去中心化应用的完整指南
开发语言·python·去中心化·内存·寻址·存储·ipfs
计算机编程小咖1 小时前
《基于大数据的农产品交易数据分析与可视化系统》选题不当,毕业答辩可能直接挂科
java·大数据·hadoop·python·数据挖掘·数据分析·spark
zhangfeng11332 小时前
以下是基于图论的归一化切割(Normalized Cut)图像分割工具的完整实现,结合Tkinter界面设计及Python代码示
开发语言·python·图论
flashlight_hi3 小时前
LeetCode 分类刷题:2529. 正整数和负整数的最大计数
python·算法·leetcode
Ashlee_code3 小时前
香港券商櫃台系統跨境金融研究
java·python·科技·金融·架构·系统架构·区块链
Jia-Hui Su3 小时前
GDSFactory环境配置(PyCharm+Git+KLayout)
git·python·pycharm
学习3人组4 小时前
手写数字识别代码
人工智能·python
古译汉书5 小时前
蓝桥杯算法之基础知识(2)——Python赛道
数据结构·python·算法·蓝桥杯
少陵野小Tommy5 小时前
Python能用古诗词数据库做什么7:根据标题、诗句查找诗歌
开发语言·数据库·python
IT·陈寒5 小时前
新手小白零基础搭建MCP教程
python·ai·tools·mcp