背景
作为一名 python 开发者, 在打包自己写的应用时, 我们一般会使用 pyinstaller / py2exe / cxfreeze 等工具.
但这样打包的结果, 仍存在一些问题困扰着我们:
-
如果我的应用依赖了 numpy, pyqt / pyside 等库, 打包的体积压不下来.
-> 我想要一种 “无依赖” 的打包方式, 让体积降到 10mb 以下.
-
每次更新后, 都要把依赖再打一遍, 整个过程比较长, 体验不好.
-> 我想要一个快速的打包方案, 且每次只对变化的地方进行 增量更新.
-
有时候用户电脑上已经安装了 python, 或者用户电脑上已经安装了某些依赖库. 我能不能不打包这些东西, 直接利用现成的?
-> 我想要一个工具, 来确保用户的电脑环境总是处于 准备就绪 状态.
-
有时候在客户端会报 “模块缺失”, “路径不正确” 等错误. 由于打包后的目录结构和源代码差异较大, 对我来说也很难排查.
-> 我想让打包后的目录结构和项目源代码结构保持一致.
如果你有这些需求, 那么 depsland 有希望成为你在寻找的工具.
注意: 当前产品 (v0.3.0) 仍处于早期开发阶段, 不保证满足以上列出的所有需求!
简介
depsland 是针对轻量化的应用分发方案打造的基础服务框架, 用于帮助开发者快速分发 python 应用程序, 并为用户提供简单友好的 程序安装, 升级和管理服务.
depsland 是一个开源项目 (项目地址), 它诞生于 pyportable-installer (项目地址), 现已作为独立的工具供 python 开发者下载和使用.
下载
depsland 提供了两种下载方式, 对于开发者, 可以通过 pip 下载:
pip install depsland
对于普通用户, 特别是没有任何开发经验的用户, 建议通过以下途径下载:
-
官方项目仓库: 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 的核心命令, 列表如下:
depsland init
: 初始化项目depsland build
: 构建一个项目depsland publish
: 发布你的项目&depsland install
depsland install-dist
: (用户) 安装项目
1. 初始化项目
-
打开命令行, cd 到你的项目目录, 这里以 “hello-world” 为例
cd ~/my-projects/hello-world
-
输入
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. 构建一个项目
-
编辑你的清单文件
下面给出一个示例作为参考:
{ "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 在打包自身时填写的清单信息, 可供参考:
-
完成后, 在命令行输入
depsland build
开始构建安装包:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AOvcTTAJ-1668558500307)(https://s2.loli.net/2022/11/16/C1aMmgb6UwAn5YF.png)]
-
完成后, 在项目根目录下的 “dist” 中会生成相应的结果:
此时只生成了启动器, 体积约 120kb. 注意现在这个状态是不可用的, 我们需要完成下一步后才能使用.
3. 发布你的应用
-
在命令行中输入
depsland publish
, 进行一次发布 -
此时, 在 hello world 项目的 dist 目录下, 会新增 “setup.exe”, “manifest.pkl” 文件和 “.oss” 文件夹:
-
现在, 将这些文件压缩成 zip 文件, 可以将它交给你的用户去使用了.
4. (用户) 安装应用
首先, 请确保用户的电脑上也安装了 depsland 软件 (安装步骤参考 “安装” 章节). 你需要通过主动告知, 手册引导或者用一个检测脚本等方式帮助用户完成此过程.
然后, 用户在收到你发布的安装包后 (这是一个 zip 文件), 解压并双击里面的 “setup.exe” 即可完成安装.
安装过程截图:
由于我们在清单文件中配置了 cli_tool
和 desktop
选项为 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) 通过命令行卸载
- 用户打开命令行
- 输入
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 作为远端存储的文件名, 即可实现覆盖上传.
文件有效期至 2022 年 11 月 23 日. 请及时下载. ↩︎