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文件路径"打包。

相关推荐
K2I-14 分钟前
UCI中Steel Plates Faults不平衡数据集处理
python
蓑笠翁00114 分钟前
Django REST Framework 全面指南:从模型到完整API接口开发
后端·python·django
感谢地心引力1 小时前
【Python】基于 PyQt6 和 Conda 的 PyInstaller 打包工具
数据库·python·conda·pyqt·pyinstaller
xiaohanbao092 小时前
Transformer架构与NLP词表示演进
python·深度学习·神经网络
love530love3 小时前
【笔记】 Podman Desktop 中部署 Stable Diffusion WebUI (GPU 支持)
人工智能·windows·笔记·python·容器·stable diffusion·podman
程序员晚枫3 小时前
Python 3.14正式发布!这5大新特性太炸裂了
python
先做个垃圾出来………3 小时前
SortedList
python
这里有鱼汤3 小时前
从DeepSeek到Kronos,3个原因告诉你:Kronos如何颠覆传统量化预测
后端·python·aigc
晓宜4 小时前
Java25 新特性介绍
java·python·算法
深栈4 小时前
机器学习:决策树
人工智能·python·决策树·机器学习·sklearn