
做 Avalonia 跨平台桌面开发的朋友迟早会碰上一个现实问题程序在 Windows 上跑得好好的换成统信 UOS 就不知道怎么发给用户了。你总不能让人家先装 .NET SDK再把 publish 出来的几十个文件手动复制到固定目录吧最省心的方案就是打一个 .deb 安装包让用户拿到手就能双击安装装完启动器里出现图标也能被系统统一管理、随时卸载。这篇文章就把我在统信 UOS 上把 Avalonia 程序打包成 deb 安装包的全过程讲一遍包含发布模式怎么选、deb 内部结构怎么回事、control 文件怎么写、安装脚本怎么配以及装完后常见的几个坑怎么排。适合已经能用 Avalonia 做出程序、正准备向统信或 deepin 环境分发的开发者也适合团队里负责出包运维的同学参考。1. 为什么要在统信系统上打 deb 包以及准备工作1.1 先认识一下 .deb 包到底是什么Deb 是 Debian 系发行版通用的软件包格式统信 UOS、deepin、Ubuntu 用的都是这一套。它的本质是一个 ar 归档文件里面拆成了三块debian-binary 记录格式版本control.tar.gz 放包的控制信息data.tar.gz 放实际要安装到系统的文件。安装的时候 dpkg 会把 data 部分解压到系统对应目录然后执行 control 里的安装脚本整个过程对用户来说就是双击一下的事。有人可能会问为什么不直接丢个 tar.gz 给用户因为 tar.gz 只做了文件归档依赖关系、桌面入口、图标注册、软件中心识别这些统统没有。而 deb 包是系统原生支持的格式装上之后会被 dpkg 数据库记录可以在统信的应用商店或者软件中心里被识别卸载也干净。对政企内网来说这种可追踪、可统一管理的分发方式比散装压缩包靠谱得多。另外deb 包还能声明依赖关系。比如你的 Avalonia 程序依赖 libfontconfig1、libgl1 这些系统库写进 Depends 后用户安装时如果缺了依赖源里能自动拉取补齐不会出现装上之后一启动就报缺库的尴尬。1.2 打包前的环境检查与工具准备统信的默认源里自带 dpkg 和 dpkg-deb这两个是打包的核心工具一般来说不需要额外安装。工作之前我习惯先看一眼系统信息和架构命令如下cat /etc/os-version uname -m dpkg-deb --version dotnet --info架构很重要统信 UOS 主要是 x86_64但也存在 arm64、龙芯等版本。Avalonia 发布的时候 runtime identifier 要对得上比如 x86_64 对应linux-x64arm64 对应linux-arm64。我这边常用的环境是统信 UOS 20 专业版 (x86_64)打包出来的包名带 amd64 后缀。如果统信系统上没有安装 .NET SDK直接去 dotnet 官网下载对应架构的 tar.gz 包解压就行不需要走系统源。解压之后把 dotnet 路径加进 PATH再执行dotnet --info验证。这个环节踩过坑的人都知道arm64 的机器下载 x64 的 SDK 是跑不起来的务必确认架构配对。提示打包全过程建议用普通用户执行然后通过 sudo 做安装验证不要在 root 环境下直接操作文件。普通用户环境更贴近最终用户的使用状态能提前暴露权限问题。2. Avalonia 应用的 Linux 发布姿势2.1 自包含还是框架依赖我为什么选自包含Avalonia 程序在 Linux 上的发布模式有两个方向framework-dependent框架依赖和 self-contained自包含。框架依赖模式生成的包小通常只有几 MB但目标机器上必须装有对应版本的 .NET 运行时自包含模式把整个 .NET 运行时也编进发布目录体积会重不少但目标机器上不需要预装任何 .NET 组件。我在统信 UOS 上分发一律建议自包含。原因很简单统信系统本身自带的 dotnet 往往不是最新版本不同机器上的运行时版本参差不齐。你辛辛苦苦写好的程序不能把运行环境寄托在用户机器的运气上。自包含模式缺点是包大一个 Avalonia 程序打出来通常有 70~100MB但这年头硬盘和带宽都不差换来的是装上就能跑的确定性。2.2 发布命令与输出目录发布命令我常用这条dotnet publish -c Release -r linux-x64 --self-contained true -o ./out/publish参数逐个说-c Release是发布 Release 配置-r linux-x64是运行时标识--self-contained true强制自包含-o指定输出目录。执行完之后./out/publish下会有一个可执行的二进制文件比如 MyApp旁边跟着一大串 .dll、.json 文件它们都要进安装包。这里有个细节值得注意不要轻易开启单文件发布PublishSingleFile。Avalonia 涉及 Skia 渲染等原生库单文件模式下部分原生库的释放逻辑在 Linux 上容易出现临时文件残留问题我见过多次装上之后莫名其妙起不来、或者个别功能失灵的反馈。老老实实用目录形式发布虽然文件多但稳定出问题也好排查。2.3 Avalonia 在 Linux 上离不开哪些系统库Avalonia 是托管 UI 框架但底层渲染和窗口管理还是绕不开系统原生库。打包之前心里要有这张依赖清单libfontconfig1字体配置与匹配缺失则界面文字渲染异常libfreetype6字体光栅化直接关系到文字显示libice6、libsm6X11 会话管理相关libgl1 / mesaOpenGL 渲染路径缺失会导致启动直接崩掉libgtk-3-0部分文件对话框和系统集成功能需要 GTK这些库最好写进 deb 的 Depends 字段这样安装时统信会自动拉取补齐。别贪图省事不写依赖等用户反馈装完打不开再去排查成本比写一行依赖高得多。3. .deb 包的结构解析与文件规范3.1 deb 包打包前的目录长什么样虽然 deb 安装包本身是 ar 归档但我们手工打包时只需要组织一个过渡目录staging directorydpkg-deb 会自动把它压成标准格式。这个过渡目录结构大概是这样的MyApp_deb/ ├── DEBIAN/ │ ├── control │ ├── postinst │ └── postrm └── usr/ ├── lib/ │ └── myapp/ │ ├── MyApp │ └── ...(dll、json 等发布文件) └── share/ ├── applications/ │ └── myapp.desktop └── icons/ └── hicolor/ └── 512x512/ └── apps/ └── myapp.pngDEBIAN 目录注意全大写存放控制信息不会安装到系统里去usr 目录下的内容会原样铺到系统根目录的对应位置。也就是说usr/lib/myapp/MyApp最终会出现在系统的/usr/lib/myapp/MyApp。3.2 control 文件字段逐个说清楚control 文件是整个包的身份证明写错一点 dpkg 都会认账。我贴一个常用的模板Package: myapp Version: 1.0.0 Architecture: amd64 Maintainer: Your Name youexample.com Installed-Size: 10240 Depends: libfontconfig1, libice6, libsm6, libgl1, libgtk-3-0 Section: utils Priority: optional Description: My Avalonia Application A cross-platform Avalonia desktop application for UOS.字段很容易出问题逐个说Package包名只能用小写英文字母、数字和连字符。我用 MyApp 的驼峰写法打包时就踩过坑dpkg 直接报 invalid characters。Version版本号建议和项目 tag 对齐方便追溯。Architecture只能写amd64不能写x86_64这是 dpkg 的架构命名写错了装上会提示架构不匹配。Installed-Size单位是 KB估算发布目录总大小就行写大点没坏处。Depends逗号分隔的依赖包列表建议把 2.3 节的库都写进去。Description第一行是简短描述后续行必须以一个空格开头作为详细说明缩进错了 control 文件就解析失败。注意control 文件必须用 UTF-8 编码保存换行用 LF。Windows 上编辑过再拷到 Linux 容易带上 CRLFdpkg 会报奇怪的语法错误。3.3 安装脚本 postinst 与 postrm 的职责划分postinst 在系统文件全部复制完成之后执行体积不大但作用很关键。它通常干三件事给主程序加执行权限、在 /usr/local/bin 下建一个软链方便命令行调用、刷新桌面数据库和图标缓存。postrm 是卸载时执行的反向脚本主要清理软链。两个脚本都要以#!/bin/bash开头并且文件要有可执行权限否则 dpkg 会无视它们。还要在 postinst 里注意一点脚本执行失败会直接导致整个安装事务回滚所以开头加set -e很有必要。刷新图标缓存这种操作偶尔会因环境问题失败可以在末尾加上|| true兜底别让一个缓存刷新失败毁掉整个安装流程。4. 全流程实操从 publish 到 dpkg -i4.1 建立打包工作目录并拷贝发布文件现在开始动真格的。先建一个干净的目录作为打包根然后把第二步 publish 出来的所有文件完整拷贝进去。mkdir -p MyApp_deb/usr/lib/myapp mkdir -p MyApp_deb/usr/share/applications mkdir -p MyApp_deb/usr/share/icons/hicolor/512x512/apps cp -a ./out/publish/* MyApp_deb/usr/lib/myapp/ chmod x MyApp_deb/usr/lib/myapp/MyApp拷贝务必用-a保留文件属性因为部分文件可能带只读标记丢了权限后面会引发奇怪的问题。主程序和 .so 文件尤其要保证可执行权限。4.2 写 .desktop 桌面入口文件统信 DDE 桌面环境通过 .desktop 文件识别应用图标这一步不做装完包启动器里什么都看不到。文件内容如下[Desktop Entry] TypeApplication NameMyApp CommentMy Avalonia Application Exec/usr/lib/myapp/MyApp Icon/usr/share/icons/hicolor/512x512/apps/myapp.png Terminalfalse CategoriesUtility;Office; StartupNotifytrueExec 一定要写绝对路径别写相对路径也别指望系统会自动搜 PATH。Icon 同样用绝对路径指向 deb 里带进去的图标文件。图标建议用 512x512 的 PNG开发环境下直接拿应用图标导出成 png 放到指定目录即可。Categories 可以按需写Utility;Office;这组合在统信启动器里能被正常归类和搜索到。4.3 亲手写 control 和安装脚本两个目录结构准备好后开始写 DEBIAN 下的三个文件。control 直接用 3.2 节的模板把包名和版本改掉就行not going to repeat。postinst 我一般这么写#!/bin/bash set -e chmod x /usr/lib/myapp/MyApp ln -sf /usr/lib/myapp/MyApp /usr/local/bin/myapp update-desktop-database /usr/share/applications/ /dev/null 21 || true gtk-update-icon-cache /usr/share/icons/hicolor/ /dev/null 21 || truepostrm 就干一件事#!/bin/bash set -e rm -f /usr/local/bin/myapp写完之后别忘给这两个脚本加执行权限chmod x MyApp_deb/DEBIAN/postinst MyApp_deb/DEBIAN/postrm这一步漏了安装时 postinst 脚本不会执行届时你会发现装完包没有可执行按钮、启动器里也没有图标。4.4 用 dpkg-deb 打包并验证内容目录结构完整、脚本权限到位之后执行打包命令dpkg-deb --build --root-owner-group MyApp_deb MyApp_1.0.0_amd64.deb--root-owner-group很重要它把包内文件的 owner 强制设为 root不然打出来的包里文件 owner 是你当前用户的 UID其他用户装上之后可能出现文件属主混乱。打包完成后用下面两条命令检查内容dpkg-deb --info MyApp_1.0.0_amd64.deb dpkg-deb -c MyApp_1.0.0_amd64.deb--info看 control 信息-c列出内含文件清单。我习惯在正式发布之前把这两步输出都过一遍确认版本号、依赖、关键文件都在。5. 安装测试与常见问题排查5.1 安装卸载验证流程打包不是终点装上能用才是终点。测试机上执行安装sudo dpkg -i MyApp_1.0.0_amd64.deb如果系统源里有缺失依赖dpkg 会提示依赖未满足这时执行sudo apt --fix-broken install自动补齐然后重新 dpkg -i。装完确认包已注册dpkg -l | grep myapp然后去启动器里搜索 MyApp点开跑一跑确认主流程正常。卸载测试也建议跑一遍sudo dpkg -r myapp卸载后检查/usr/local/bin/myapp软链是否被清理干净桌面图标是否消失。来回迭代验证没问题这个包才能对外发。5.2 我踩过的六个坑打包这事儿看着简单实际操作里坑一个接一个。我把最有代表性的六个列出来Package 字段用了大写字母dpkg 直接报package name has invalid characters。包名永远用小写。.desktop 文件里 Exec 写的相对路径启动器图标点了没反应改成绝对路径立刻好。忘了给 MyApp 主程序加执行权限安装正常但双击无反应命令行直接提示 Permission denied。control 文件 Windows 编辑带 CRLFdpkg --build 不报错安装时提示invalid control file。写完后用file命令看一眼格式最保险。架构写成 x86_64安装提示 architecture mismatch改成 amd64 才通过。图标路径不匹配图标实际在 512x512 目录.desktop 却指向其他尺寸启动器里只显示默认齿轮图标。5.3 装上之后启动黑屏或者闪退怎么办这个问题在统信 UOS 上遇到得不算少通常和图形环境或者渲染库有关。统信 DDE 默认可能跑在 X11也可能跑在 Wayland而 Avalonia 程序在不同后端下的表现并不一致。如果启动黑屏先检查系统是否装了 mesa 等 OpenGL 库缺了直接补上sudo apt install mesa-utils libgl1如果补了图形库还黑屏可以试试在 .desktop 文件的 Exec 行后面追加环境变量强制 X11 后端Execenv DISPLAY:0 /usr/lib/myapp/MyApp这个变量对部分用户环境有效但最有效的方法是在程序里手动指定 Avalonia 的 backend 优先级。在 Main 函数里可以使用 AppBuilder 配置 X11 优先.UseX11()这样至少能保证在没有 Wayland 支持的情况下程序能落到 X11 路径不至于一启动就黑。结尾再分享一个习惯我实际用这套流程出过几次包之后就把 publish 和打包整个过程写成了一个 build-deb.sh 脚本放在仓库的 build 目录下每次发版只需要改版本号和包名一条命令出包。包里文件多了之后还可以顺便在脚本里把 md5sums 生成出来放进 DEBIAN 目录方便 dpkg 完整性校验。用 dpkg-deb 手工打 deb 包并不难难的是把流程规范化和自动化。把这套沉淀下来后续不管出多少个统信版本都是改个版本号的事。