作为一名 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 有很多相似之处.
在命令行中输入 depsland -h 或者 python3 -m depsland -h 获得所有命令的帮助信息. 你也可以输入 depsland 获得某个具体的命令的帮助.
下面会介绍到 depsland 的核心命令, 列表如下:
depsland init: 初始化项目depsland build: 构建一个项目depsland publish: 发布你的项目depsland installdepsland install-dist: (用户) 安装项目打开命令行, 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 使用它来构建和打包.
编辑你的清单文件
下面给出一个示例作为参考:
{
"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. 注意现在这个状态是不可用的, 我们需要完成下一步后才能使用.
在命令行中输入 depsland publish, 进行一次发布

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

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

首先, 请确保用户的电脑上也安装了 depsland 软件 (安装步骤参考 “安装” 章节). 你需要通过主动告知, 手册引导或者用一个检测脚本等方式帮助用户完成此过程.
然后, 用户在收到你发布的安装包后 (这是一个 zip 文件), 解压并双击里面的 “setup.exe” 即可完成安装.
安装过程截图:

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

通过命令行启动:
depsland run hello_world

关于安装耗时的说明
安装耗时取决于 1) 你的依赖列表是否包含了体积大的依赖库; 2) 是否是首次安装.
通常来说, 包含了较大依赖的情况下, depsland 调用 pip 从 pypi 下载的时间会变长 (几秒到几分钟不等); 而在非首次安装的情况下, depsland 会充分复用已安装的资源, 整个过程则会非常快 (最快甚至能达到毫秒级别).
开发者将第二节 (“2. 打包项目”) 中的清单文件进行编辑, 并提升版本号 (例如修改为 “0.2.0”), 重新 depsland build 一下, 得到新的安装包.
新的安装包发给用户后, 用户解压并双击 “setup.exe” 完成升级.
depsland 暂未提供可视化界面的卸载方案. 用户目前只能通过以下方式卸载:
a) 通过命令行卸载
depsland uninstall hello_world 完成卸载b) 手动卸载
用户删除以下路径:
C:\ProgramData\Depsland\apps\hello_world\0.1.0C:\ProgramData\Depsland\apps\.bin\hello_world.exeC:\ProgramData\Depsland\apps\.venv\hello_world注: 下图中, |= 表示文件夹, |- 表示文件, # 后面是注释.
depsland # 这里是根目录, 在安装完成后, 会加入到用户的 PATH 环境变量中.
|= apps
|= .bin # 在这里放置可执行文件, 例如 "hello_world.exe".
| # 该路径也会加入到用户的 PATH 环境变量中, 因此用户可以在控制台输入 "hello_world" 直接调用.
|- hello_world.exe
|= .venv # 在这里创建每个 app 的 python 虚拟环境.
|= hello_world
|= lk_logger
|= numpy
|= ...
# 除 ".bin" 和 ".venv" 这两个以点号开头的特殊目录外, 其他目录都是第三方应用目录.
# 这些第三方应用的目录是以 作为名称.
|= 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 安装的结果
| # 在这里以 // 的形式存在.
|= index # 索引目录, 用于查询第三方库之间的依赖关系和路径地图.
|= python # 一个便携式 python 解释器 (3.10 版本)
|= temp # 临时文件目录, 用于放置运行时产生的临时文件. 在 depsland 运行结束后会自动清理.
|- .depsland_project # 用于帮助 depsland 检查自己是处于项目开发模式还是发行包模式, 具体见该文件内的说明.
|- manifest.json # depsland 自身的清单文件
|- CHANGELOG.zh.md # 更新日志
|- README.zh.md # 自述文档
depsland init 会在目标项目的根目录下生成一个 “manifest.json”, 叫做清单文件.
清单文件包含以下键:
depsland/apps/ 应用目录.depsland build 命令会在目标项目的根目录下的 dist 目录 (如果没有会自动创建) 生成待发布目录.
示例如下:
hello_world_project
|= dist
|= hello_world-0.1.0 # 生成该目录
|- launcher.exe # 目录下有一个启动器文件
请注意此时的启动器是不可用的. 因为我们还没有打包清单文件中的资产.
depsland publish 会根据清单文件内容, 再与旧版本的清单文件 (如果曾经发布过的话) 进行比对, 找到 “新增的”/“变化的”/“移除的” 资产内容, 进行 增量更新.
更新过的资产会被保存到 depsland/oss/apps/ 目录. 同时会被软链接到 hello_world_project/dist/hello_world-0.1.0/.oss 目录.
此时 dist 内容如下:
hello_world_project
|= dist
|= hello_world-0.1.0
|= .oss # 软链接, 来自 `depsland/oss/apps/`
|- manifest.pkl # 结构化的清单文件 (二进制格式)
|- setup.exe # 安装器
|- launcher.exe
depsland 根据清单文件 (主要是 “assets” 键) 的内容生成一个叫做 AssetInfo 的对象, 每个 AssetInfo 对象存储了以下信息:
当进行比对时, 会综合比较 scheme, hash, utime 的差异, 以此确定新旧版本的清单文件是否存在差异.

请注意 uid 在相同项的比对中是一定相同的, 所以对于有差异的项, 将 uid 作为远端存储的文件名, 即可实现覆盖上传.
文件有效期至 2022 年 11 月 23 日. 请及时下载. ↩︎