Python 增量更新/打包解决方案 -- Depsland

背景

作为一名 python 开发者, 在打包自己写的应用时, 我们一般会使用 pyinstaller / py2exe / cxfreeze 等工具.

但这样打包的结果, 仍存在一些问题困扰着我们:

  • 如果我的应用依赖了 numpy, pyqt / pyside 等库, 打包的体积压不下来.

    -> 我想要一种 “无依赖” 的打包方式, 让体积降到 10mb 以下.

  • 每次更新后, 都要把依赖再打一遍, 整个过程比较长, 体验不好.

    -> 我想要一个快速的打包方案, 且每次只对变化的地方进行 增量更新.

  • 有时候用户电脑上已经安装了 python, 或者用户电脑上已经安装了某些依赖库. 我能不能不打包这些东西, 直接利用现成的?

    -> 我想要一个工具, 来确保用户的电脑环境总是处于 准备就绪 状态.

  • 有时候在客户端会报 “模块缺失”, “路径不正确” 等错误. 由于打包后的目录结构和源代码差异较大, 对我来说也很难排查.

    -> 我想让打包后的目录结构和项目源代码结构保持一致.

如果你有这些需求, 那么 depsland 有希望成为你在寻找的工具.

注意: 当前产品 (v0.3.0) 仍处于早期开发阶段, 不保证满足以上列出的所有需求!

简介

depsland 是针对轻量化的应用分发方案打造的基础服务框架, 用于帮助开发者快速分发 python 应用程序, 并为用户提供简单友好的 程序安装, 升级和管理服务.

depsland 是一个开源项目 (项目地址), 它诞生于 pyportable-installer (项目地址), 现已作为独立的工具供 python 开发者下载和使用.

下载

depsland 提供了两种下载方式, 对于开发者, 可以通过 pip 下载:

pip install depsland

对于普通用户, 特别是没有任何开发经验的用户, 建议通过以下途径下载:

  • 国内下载渠道: 阿里云 oss1

  • 官方项目仓库: github 发布页

    注意: 由于本人的网络问题, github 仓库只发布无嵌入式解释器的版本, 且发布频率可能落后于国内. 推荐使用国内下载渠道.

注: 截至 2022 年 11 月, 最新发布版本为 0.3.x.

安装

如果你是通过 pip 安装, 本小节可跳过.

你下载得到的是一个 zip 文件 (体积约 65mb), 解压后将得到以下目录:

双击根目录下的 “setup.exe” 开始安装.

请注意选择合适的目录安装, depsland 默认会安装在 C:\ProgramData\Depsland 目录, 但考虑到权限问题, 我们建议你安装在其他盘符下面.

等待约 30s ~ 1min 安装完成. 新开一个 命令行, 输入 depsland version, 如果显示以下信息, 则说明安装成功:

此外, 你还可以输入 depsland -h, 获得所有可用的命令的帮助信息:

升级

如果你是通过 pip 安装, 请使用 pip install -U depsland 进行升级.

如果你是通过另一种方式安装, 当前版本 (0.3.x) 暂未提供自升级功能. 你需要手动卸载 depsland 后, 从网站下载最新版本的安装包.

注: 我们预计在 0.4.0 时提供自升级功能.

卸载

如果你是通过 pip 安装, 请使用 pip uninstall depsland 进行卸载.

如果你是通过另一种方式安装, depsland 暂未提供关于自身的自动卸载方案. 你需要手动删除以下路径:

  • C:\ProgramData\Depsland (或者你的自定义安装目录)
  • 环境变量: DEPSLAND, PATH (~\Depsland\depsland.exe, ~\Depsland\apps\.bin)

使用

depsland 采用类似 poetry 的项目管理方式, 在下面的介绍中, 你会发现它的命令设计与 poetry 有很多相似之处.

0. 使用帮助

在命令行中输入 depsland -h 或者 python3 -m depsland -h 获得所有命令的帮助信息. 你也可以输入 depsland <command> -h 获得某个具体的命令的帮助.

下面会介绍到 depsland 的核心命令, 列表如下:

  1. depsland init: 初始化项目
  2. depsland build: 构建一个项目
  3. depsland publish: 发布你的项目
  4. depsland install & depsland install-dist: (用户) 安装项目

1. 初始化项目

  1. 打开命令行, cd 到你的项目目录, 这里以 “hello-world” 为例

    cd ~/my-projects/hello-world
    
  2. 输入 depsland init, 它将会在该目录下创建一个 “manifest.json” 文件

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8GIGYlQ-1668558500306)(https://s2.loli.net/2022/11/16/WFDx3Vcdjk1wMRB.png)]

    假设我们的项目目录结构如下:

    ~/my-projects/hello-world
    |= src
       |- main.py
    |- README.md
    |- requirements.txt
    |- manifest.json  # <- 新增一个清单文件
    

    这个清单文件 (“manifest.json”) 作用和 poetry 的 “pyproject.toml” 相似, 它记录了你的项目中的所有资产情况. depsland 使用它来构建和打包.

2. 构建一个项目

  1. 编辑你的清单文件

    下面给出一个示例作为参考:

    {
        "appid": "hello_world",  // 应用 id. 采用下划线命名法
        "name": "Hello World",  // 应用名称. 建议使用正常的大小写 (标题命名法)
        "version": "0.1.0",  // 版本号遵循 semver 规范
        "assets": {
            // 键: 填相对于本文件的路径. 请务必将所有要打包的路径都加入进来.
            //     该路径可以是文件, 也可以是目录.
            // 值: 有以下可选值:
            //     all          打包该目录下的全部文件.
            //     all_dirs     保留该目录下的子目录结构, 但不包含文件.
            //     top          打包该目录下的全部文件, 但不包含子目录 (会创建空文件夹).
            //     top_files    打包该目录下的全部文件, 但不包含子目录 (不会创建空文件夹).
            //     top_dirs     保留该目录下的一级子目录结构, 但不包含文件.
            //     root         保留根目录结构, 但不包含子目录或文件 (即只创建根目录作为空文件夹).
            //     此外, 你也可以使用空字符串, 表示 "all".
            "src": "all",
            "README.md": "all",
        },
        "dependencies": {
            // 依赖列表. 按照与 requirements.txt 中相同的定义方式来写.
            // 键: 依赖包名.
            // 值: 版本范围, 留空则表示最新. 如果有多个范围值, 用逗号分隔.
            "argsense": "==0.5.0a0",
            "numpy": "",
            "pyside6": ">=6.1.3",
            "lk-logger": "",
            "lk-utils": ">=2.4.0,<2.5.0",
        },
        "pypi": [
            // 如果你有一些非 pypi 索引的库 (比如你自己写的但没有发布到 pypi 的库), 在这里填写.
            // 格式: 绝对路径或相对 (于本文件) 的路径. 必须是有效的 whl 或 tar.gz 文件.
            "./addons/argsense-0.5.0a0-py3-none-any.whl",
        ],
        "launcher": {  // 在这里定义你的应用该如何被启动.
            "script": "src/main.py --name Alice",  // 格式: <脚本文件> <可选参数>
            "icon": "",  // 启动器图标 (可选), 必须是 ".ico" 格式.
            "cli_tool": true,  // 你的工具是否可以在命令行运行. 开启后, 可使用 `hello_world` 来启动.
            //  注: 如果没开启, 则可以通过 `depsland run hello_world` 来启动.
            "desktop": true,  // 是否生成桌面快捷方式.
            //  如果开启此选项, 建议 icon 也填写. 为你的启动器生成漂亮的图标.
            "start_menu": false,  // 是否添加到开始菜单 (注: 实验性功能!)
            "show_console": true,  // 你的应用启动后, 是否显示一个控制台窗口.
            //  如果你的应用有 gui 界面, 此选项建议关闭.
        }
    }
    

    下面这张截图是 depsland 在打包自身时填写的清单信息, 可供参考:

  2. 完成后, 在命令行输入 depsland build 开始构建安装包:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOvcTTAJ-1668558500307)(https://s2.loli.net/2022/11/16/C1aMmgb6UwAn5YF.png)]

  3. 完成后, 在项目根目录下的 “dist” 中会生成相应的结果:

    此时只生成了启动器, 体积约 120kb. 注意现在这个状态是不可用的, 我们需要完成下一步后才能使用.

3. 发布你的应用

  1. 在命令行中输入 depsland publish, 进行一次发布

  2. 此时, 在 hello world 项目的 dist 目录下, 会新增 “setup.exe”, “manifest.pkl” 文件和 “.oss” 文件夹:

  3. 现在, 将这些文件压缩成 zip 文件, 可以将它交给你的用户去使用了.

4. (用户) 安装应用

首先, 请确保用户的电脑上也安装了 depsland 软件 (安装步骤参考 “安装” 章节). 你需要通过主动告知, 手册引导或者用一个检测脚本等方式帮助用户完成此过程.

然后, 用户在收到你发布的安装包后 (这是一个 zip 文件), 解压并双击里面的 “setup.exe” 即可完成安装.

安装过程截图:

由于我们在清单文件中配置了 cli_tooldesktop 选项为 true. 所以用户可以通过命令行或桌面快捷方式来启动 hello world:

  • 通过桌面图标启动:

  • 通过命令行启动:

    depsland run hello_world
    

关于安装耗时的说明

安装耗时取决于 1) 你的依赖列表是否包含了体积大的依赖库; 2) 是否是首次安装.

通常来说, 包含了较大依赖的情况下, depsland 调用 pip 从 pypi 下载的时间会变长 (几秒到几分钟不等); 而在非首次安装的情况下, depsland 会充分复用已安装的资源, 整个过程则会非常快 (最快甚至能达到毫秒级别).

5. 升级应用

开发者将第二节 (“2. 打包项目”) 中的清单文件进行编辑, 并提升版本号 (例如修改为 “0.2.0”), 重新 depsland build 一下, 得到新的安装包.

新的安装包发给用户后, 用户解压并双击 “setup.exe” 完成升级.

6. 卸载应用

depsland 暂未提供可视化界面的卸载方案. 用户目前只能通过以下方式卸载:

a) 通过命令行卸载

  1. 用户打开命令行
  2. 输入 depsland uninstall hello_world 完成卸载

b) 手动卸载

用户删除以下路径:

  • C:\ProgramData\Depsland\apps\hello_world\0.1.0
  • C:\ProgramData\Depsland\apps\.bin\hello_world.exe
  • C:\ProgramData\Depsland\apps\.venv\hello_world

原理说明

depsland 的项目结构

注: 下图中, |= 表示文件夹, |- 表示文件, # 后面是注释.

depsland  # 这里是根目录, 在安装完成后, 会加入到用户的 PATH 环境变量中.
|= apps
   |= .bin  # 在这里放置可执行文件, 例如 "hello_world.exe". 
   |        # 该路径也会加入到用户的 PATH 环境变量中, 因此用户可以在控制台输入 "hello_world" 直接调用.
      |- hello_world.exe
   |= .venv  # 在这里创建每个 app 的 python 虚拟环境.
      |= hello_world
         |= lk_logger
         |= numpy
         |= ...
   # 除 ".bin" 和 ".venv" 这两个以点号开头的特殊目录外, 其他目录都是第三方应用目录. 
   # 这些第三方应用的目录是以 <appid> 作为名称.
   |= hello_world  # 在该目录下就是应用有关的文件, 里面的内容各不相同, 与应用自身有关.
      |= src
         |- main.py
      |- README.md
      |- CHANGELOG.md
   |= ...  # 更多应用
|= build  # 该目录下存放用于构建 depsland 自身的脚本和资产文件.
|= chore  # 一些杂项.
|= conf  # 配置文件. 目前仅有一个配置 "depsland.yaml".
   |- depsland.yaml  # 该文件会被 depsland.config 模块读取.
|= depsland  # depsland 的开源代码.
|= docs  # 技术文档和用户手册.
|= oss  # 本地的第三方资源存储目录, 用来模拟 oss 服务的存储.
|       # 当我们使用 depsland publish 的时候, 应用的资产文件都会被复制一份到这里.
|= pypi  # 缓存从 pypi 网站下载的 python 第三方库, 用于跨应用的依赖复用, 以及快速生成 apps/.venv 下的虚拟环境.
   |= cache  # pip 命令的自定义缓存目录
   |= downloads  # pip 下载的 whl, tar.gz, zip 文件
   |= installed  # pip 安装的结果
   |             # 在这里以 <package_name>/<version>/<pip_install_results> 的形式存在.
   |= index  # 索引目录, 用于查询第三方库之间的依赖关系和路径地图.
|= python  # 一个便携式 python 解释器 (3.10 版本)
|= temp  # 临时文件目录, 用于放置运行时产生的临时文件. 在 depsland 运行结束后会自动清理.
|- .depsland_project  # 用于帮助 depsland 检查自己是处于项目开发模式还是发行包模式, 具体见该文件内的说明.
|- manifest.json  # depsland 自身的清单文件
|- CHANGELOG.zh.md  # 更新日志
|- README.zh.md  # 自述文档

depsland 命令详解

1. depsland init

depsland init 会在目标项目的根目录下生成一个 “manifest.json”, 叫做清单文件.

清单文件包含以下键:

  • appid: 一个独立的应用 id. 用于创建其在用户端的 depsland/apps/<appid> 应用目录.
  • name: 用于启动器名称, 例如 “Hello World.exe”. 当创建桌面快捷方式时, 用户将在桌面看到 “Hello World” 启动器图标.
  • version: 版本号.
  • assets: 资产文件, 指要打包的所有目录/文件清单. 请务必填写所有需要的资源, 否则在用户端会报文件/模块缺失错误.
  • dependencies: python 依赖列表. 参考 requirements.txt 的方式来填写. 请确保这些依赖都能在官方 pypi (或者镜像站) 中能够下载到.
  • pypi: 自定义的依赖资源. 指未上传到 pypi 网站的自定义包, 格式为 “.whl” 或 “.tar.gz”.
  • launcher: 启动器设置. 用于指示 depsland 如何创建启动器. 目前支持的形式有: 命令行, 桌面快捷方式, 开始菜单.
2. depsland build

depsland build 命令会在目标项目的根目录下的 dist 目录 (如果没有会自动创建) 生成待发布目录.

示例如下:

hello_world_project
|= dist
   |= hello_world-0.1.0  # 生成该目录
      |- launcher.exe  # 目录下有一个启动器文件

请注意此时的启动器是不可用的. 因为我们还没有打包清单文件中的资产.

3. depsland publish

depsland publish 会根据清单文件内容, 再与旧版本的清单文件 (如果曾经发布过的话) 进行比对, 找到 “新增的”/“变化的”/“移除的” 资产内容, 进行 增量更新.

更新过的资产会被保存到 depsland/oss/apps/<appid> 目录. 同时会被软链接到 hello_world_project/dist/hello_world-0.1.0/.oss 目录.

此时 dist 内容如下:

hello_world_project
|= dist
   |= hello_world-0.1.0
      |= .oss  # 软链接, 来自 `depsland/oss/apps/<appid>`
      |- manifest.pkl  # 结构化的清单文件 (二进制格式)
      |- setup.exe  # 安装器
      |- launcher.exe

depsland 是如何实现增量更新的

depsland 根据清单文件 (主要是 “assets” 键) 的内容生成一个叫做 AssetInfo 的对象, 每个 AssetInfo 对象存储了以下信息:

  • 资产备份策略 (scheme: all, all_files, all_dirs, top, top_files, top_dirs, root)
  • 文件的哈希值 (hash)
  • 文件的最近修改时间 (utime)
  • 文件的路径哈希值 (uid)

当进行比对时, 会综合比较 scheme, hash, utime 的差异, 以此确定新旧版本的清单文件是否存在差异.

请注意 uid 在相同项的比对中是一定相同的, 所以对于有差异的项, 将 uid 作为远端存储的文件名, 即可实现覆盖上传.


  1. 文件有效期至 2022 年 11 月 23 日. 请及时下载. ↩︎

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值