Python使用PyInstaller打包exe和依赖,并实现版本管理

最近公司的一个Python写的GUI工具需要分发给公司员工使用,目前使用单文件打包模式,生成一个exe文件(仅Windows下使用),打包之后还有部分依赖的文件和文件夹需要一起分发,目前分发以及后面的自动更新不是很方便,因此使用Python再写了一个打包脚本简化打包操作。

主要功能

  1. 使用PyInstaller打包成单个exe文件
  2. exe要支持Windows的属性查看版本信息
  3. exe文件和依赖文件和文件夹打包成一个zip文件
  4. 发布后支持检查版本更新

PyInstaller打包

PyInstaller文档:https://pyinstaller.org/en/stable/usage.html

目前使用命令行通过pyinstaller命令打包,要把打包功能集成到build.py脚本中,最好使用Python代码实现打包

命令行打包

注意:如果安装后没有pyinstaller命令,可能是非管理员用户,又没有【以管理员身份运行】pip安装命令

命令行打包(-F是打包成单文件,-w是以窗口模式运行):

pip install pyinstaller
pyinstaller -F -w app.py

代码打包

使用Python代码打包,需要导入PyInstaller__main__模块,然后用__main__run方法,参数传递是以数组的形式传递,比较方便

这里使用config.py文件配置软件的图标以及Windowsversion文件,后面会介绍如何获取到Windowsversion文件

from PyInstaller import __main__

def pyinstaller_package():
    # 使用pyinstaller打包
    __main__.run(['-F', '-w', f'--icon={config.ICO_FILE}', f'--version-file={config.VERSION_FILE_TXT}', 'app.py'])

这样就实现了用build.py文件打包成单个文件exe

版本管理

要实现版本管理以及版本检查更新,这里使用一个versions.py文件记录需要发布的版本,然后生成一个versions.json文件,和zip文件一起发布到服务器上。

版本管理

一般情况下版本管理可以放到数据库,但是因为工具比较小,没有必要搞那么复杂,因此用versions.py文件管理版本列表,每次发布的时候新增一条发布版本记录即可。

版本列表

import config

release_list = [{
    "version": "1.0.1",
    "publishDate": "2023-07-07 10:00",
    "forceUpdate": True,
    "publishNotes": ["1. 版本更新", "2. 版本1.0.1", "3. 更新内容:xxxx"],
    "updateUrl": f'{config.INTERNAL_DOWNLOAD_URL}/app.1.0.1.20230707.zip'
}, {
    "version": "1.0.0",
    "publishDate": "2023-07-05 17:00",
    "forceUpdate": True,
    "publishNotes": ["1. 初始版本", "2. 生成1.0.0", "3. 更新内容:xxxx"],
    "updateUrl": f'{config.INTERNAL_DOWNLOAD_URL}/app.1.0.0.20230705.zip'
}]

current_version = release_list[0]['version']

生成versions.json

通过versions.py里面的release_list信息生成一个json文件,这个文件最后发布到Nginx服务,用于检查版本

import json
import os
from versions import release_list, current_version

def generate_version_json(dist_path):
    versions_json = json.dumps(release_list, indent=4, ensure_ascii=False)
    if not os.path.exists(dist_path):
        os.mkdir(dist_path)
    with open(os.path.join(dist_path, 'versions.json'), 'w', encoding="utf8") as json_file:
        json_file.write(versions_json)

生成的versions.json示例,放到Nginx服务器上能够通过URL访问到就可以

[
    {
        "version": "1.0.1",
        "publishDate": "2023-07-07 10:00",
        "forceUpdate": true,
        "publishNotes": [
            "1. 版本更新",
            "2. 版本1.0.1",
            "3. 更新内容:xxxx"
        ],
        "updateUrl": "https://xxxxx/app.1.0.1.xxxx.zip"
    },
    {
        "version": "1.0.0",
        "publishDate": "2023-07-05 17:00",
        "forceUpdate": true,
        "publishNotes": [
            "1. 初始版本",
            "2. 生成1.0.0",
            "3. 更新内容:xxxx"
        ],
        "updateUrl": "https://xxxx/app.1.0.0.xxxx.zip"
    }
]

Windows版本文件

为了使exe文件看起来比较正式,可以考虑增加图标以及Windows的版本文件,打包后就有版权等信息了。

什么是版本文件

通常在Windows系统下,一个exe文件通过属性可以看到软件的作者、版本、版权等等信息,我们自己用PyInstaller打包的exe文件如果没有指定–version-file的话,属性里面没有这样的信息,如图:

image-20230708113615921

抓取版本信息

安装好PyInstaller之后在Python的安装目录下面的Scripts中会有一个pyi-grab_version.exe文件,可以抓取其他第三方的exe(我这里使用的迅雷)的版本信息,并生成一个file_version_info.txt文件,我们可以这个版本文件为基础,修改为自己的版本文件

pyi-grab_version.exe "C:\softs\Thunder Network\Thunder\Program\Thunder.exe"

版本文件如下,如果自己生成失败,可以直接用这里贴出来的文件信息做修改:

# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
  ffi=FixedFileInfo(
    # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
    # Set not needed items to zero 0.
    filevers=(11, 4, 7, 2104),
    prodvers=(11, 4, 7, 2104),
    # Contains a bitmask that specifies the valid bits 'flags'r
    mask=0x3f,
    # Contains a bitmask that specifies the Boolean attributes of the file.
    flags=0x0,
    # The operating system for which this file was designed.
    # 0x4 - NT and there is no need to change it.
    OS=0x40004,
    # The general type of file.
    # 0x1 - the file is an application.
    fileType=0x1,
    # The function of the file.
    # 0x0 - the function is not defined for this fileType
    subtype=0x0,
    # Creation date and time stamp.
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        '080404b0',
        [StringStruct('CompanyName', '深圳市迅雷网络技术有限公司'),
        StringStruct('FileDescription', '迅雷11'),
        StringStruct('FileVersion', '11,4,7,2104'),
        StringStruct('InternalName', 'Thunder 2'),
        StringStruct('LegalCopyright', '版权所有 (C) 2023 深圳市迅雷网络技术有限公司'),
        StringStruct('OriginalFilename', 'Thunder'),
        StringStruct('ProductName', '迅雷11'),
        StringStruct('ProductVersion', '11.4.7.2104'),
        StringStruct('LegalTrademarks', '迅雷11'),
        StringStruct('SpecialBuild', '100017')])
      ]), 
    VarFileInfo([VarStruct('Translation', [2052, 1200])])
  ]
)

自动写入

修改好基本信息之后,可以在打包的时候用正则表达式替换版本信息,自动实现新版本信息写入file_version_info.txt

def process_version_info():
    ver = current_version.split('.')
    with open('file_version_info.txt', 'r+', encoding='utf8') as ver_file:
        txt = ver_file.read()
        txt = re.sub('\\(\\d+, \\d+, \\d+, 0\\),', f'({ver[0]}, {ver[1]}, {ver[2]}, 0),', txt)
        txt = re.sub("u'\\d+\\.\\d+\\.\\d+\\.0'", f"u'{current_version}.0'", txt)
        txt = re.sub("\\(u'FileDescription', u'.+'\\)", f"(u'FileDescription', u'{config.PRODUCT_NAME}')", txt)
        txt = re.sub("\\(u'ProductName', u'.+'\\)", f"(u'ProductName', u'{config.PRODUCT_NAME}')", txt)
        ver_file.seek(0)
        ver_file.truncate()
        ver_file.write(txt)

打包zip

通过PyInstaller打包生成的app.exe已经在dist目录,我们需要把app.exe和依赖的文件或文件夹打包成zip文件,并放到dist目录,打包代码:

import os
import shutil
import time
import zipfile

OUT_PATH = 'dist'  # 输出路径
ZIP_FILES = ['files', 'resources', 'dist\\app.exe', 'data.xlsx']  # 压缩包需要文件
CLEAN_PATH = ['dist', 'build']  # 清理路径

def zip_dir_list(input_path_list: list, output_file):
    with zipfile.ZipFile(output_file, "w", zipfile.ZIP_DEFLATED) as output_zip:
        for input_path in input_path_list:
            if os.path.isdir(input_path):
                zip_dir(input_path, output_zip)
            elif os.path.isfile(input_path):
                filename = input_path.split(os.sep)[-1]
                print('zip adding file %s' % input_path)
                output_zip.write(input_path, filename)

def zip_dir(input_path, output_zip):
    for path, dir_names, file_names in os.walk(input_path):
        for filename in file_names:
            full_path = os.path.join(path, filename)
            print('zip adding file %s' % full_path)
            # 文件路径,压缩路径
            output_zip.write(full_path)

if __name__ == '__main__':
    date_str = time.strftime('%Y%m%d')
    zip_dir_list(ZIP_FILES, f'{OUT_PATH}/app.{current_version}.{date_str}.zip')  # zip打包相关文件

把需要打包的文件写到ZIP_FILES列表中即可。

完整步骤如下:

def clean_last_build():
    # 清理上次文件
    for c_path in CLEAN_PATH:
        if os.path.exists(c_path):
            print('clean path %s' % c_path)
            shutil.rmtree(c_path)

if __name__ == '__main__':
    clean_last_build()  # 清理上次构建目录
    generate_version_json(OUT_PATH)  # 生成版本文件
    process_version_info()  # 处理windows版本生成文件
    pyinstaller_package()  # 调用pyinstaller打包
    date_str = time.strftime('%Y%m%d')
    zip_dir_list(ZIP_FILES, f'{OUT_PATH}/app.{current_version}.{date_str}.zip')  # zip打包相关文件
    print('打包完成!')

build.py主要就是按照顺序调用各个步骤的方法实现整个打包过程

检查更新

检查版本更新,这里使用semver判断是否有新版本:

import requests
import semver

class Updater:
    def __init__(self, base_url: str):
        self.base_url = base_url
    def check_for_update(self, version):
        check_url = self.base_url + '/versions.json'
        res = requests.get(check_url).json()
        if len(res) and semver.compare(res[0]['version'], version) > 0:
            return res[0]
if __name__ == '__main__':
    updater = Updater('https://xxxx/app')
    result = updater.check_for_update('1.0.0')
    if result is not None:
        print('有新版本:', result)
    else:
        print('已经是最新版本')

这样就实现了检查版本更新。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇