Linux下软件打包
准备
测试使用的是我的第一个软件:BlackWidow
编译后得到可执行文件
运行一下
获取动态库文件
要想在别的系统中也可以正常使用,需要把编译时的库文件集中在一起,
具体操作参考Linux下批量获取程序依赖动态库
执行之后,所有的库文件就到了可执行文件目录下
把数据库、配置文件等等需要的所有文件都复制到加载目录
deb
此格式是Debian系发行版特有的,包括Linux Mint/Ubuntu/Deepin等等。
创建一个根目录 //root
例如/home/jackey/application
创建DEBIAN文件夹,添加control文件,必须是utf-8编码
1 |
|
要留有空行
- Package:指该软件包的名字。如果你的软件包名称有两个词,用一个连字符(-)把它们连起来。软件包的名称只能有小写的英文字母,数字(不管你相信不相信)以及"+“和”-"。
- Version:显然是程序的版本。确保这里的值不包括连字符。
- Section:列出了你的软件属于的类别,可能的值包括admin(管理),games(游戏),gnome,kde,mail(电子邮件),misc(杂项)<译者注:misc是miscellaneous的简称>,net(网络),sound(声音),text(文本),utils(实用工具)和web(万维网)。
- Priority:声明这个包的优先级(大部分的时候使用optional(可选的))。
- Architecture:是该程序可运行的CPU架构(可能的值为i386,amd64和powerpc)<译者注:此处虽然是专有名词,但是由于linux是大小写敏感的,所以文件内还是应该小写>。
- Eseential:指该软件包是否是必须的(大部分的时候不是)。
- Depends:意味着要使用这个程序必须拥有的程序,
- Recommends:指除了极特殊情况下的安装之外,大多数时候都需要的依赖关系。
- Suggests:意味可能给这个软件带来更多功能的,但是没有完全没有问题的依赖关系<译者注:同Recommands>。你可以用英文的半角逗号分开不同的依赖关系(,)。如果两个软件中的任何一个可以工作,用竖线"|"分开它们。你也可以指定特定的版本,用在括号里用<<(小于),<=(小于或等于),=(等于),>=(大于或等于),或>>(大于)来表示。
- Install-Size是程序安装后的大小,按KB算。
- Maintainer:就是你(写上你的名字,后面的方括号"[ ]"里留下你的电子邮件地址)。
- Conflicts:表示跟这个程序冲突的软件。
- Replaces:表明哪些软件包将被这个程序取代。
- Description:显示你的描述。
这样你的control文件就完成了,把它保存到一个名为"/DEBIAN"的文件夹里(注意必须大写)。例如,如果你正在建立一个软件包,并且你的要打包的软件是在/fungame/,那么你应该把你的control文件放在/fungame/DEBIAN/下面。
现在是时候添加实际程序了。复制要打包的程序到你的工作文件夹(上文中的/fungame)。比如说,如果软件X位于/usr/local/X/,可执行文件在/usr/bin/X,你的.deb文件夹是/fungame/,把/usr/local/X/的所有东西复制到/fungame/usr/local/X/,同时复制文件夹/usr/bin/fungame/下的所有东西到/fungame/usr/bin。
你也可以建一个菜单条目(一个.desktop文件),这里的例子位于~/fungame/usr/share/applications/fungame.desktop。这个文件的内容如下:
1 |
|
你可以忽略"Icon"那一行
目录结构如下
1 |
|
构建deb包
1 |
|
rpm
此格式是fedora/centos等红帽系特有软件包格式。
依赖
先安装依赖
1 |
|
程序
首先准备需要打包的程序hello.sh
1 |
|
编写spec文档
如果没有就创建hello.spec(注意:我们的spec文档的名字要和安装程序的名称相同)
1 |
|
表头包含安装包的基本信息,表头项说明如下(* 标识必须包含此项)
- Name *:软件包的名称
- Version *:软件版本号
- Release *:同一版本软件的发布版本号
- Summary *:软件的简短介绍
- License *:软件的授权模式
- Summary(zh_CN):中文版本的简短介绍
- Requires(pre | post | preun | postun):/bin/sh 各个过程需要使用哪种shell脚本
内容包含以下项目
- %description 软件的完整介绍,与Summary字段不通,完成内容介绍可以写多行。
- %prep(没有使用) 解压源码的过程。
- %build (没有使用) 编译源码的过程。
- %install 安装脚本。将需要安装的文件拷贝到%{buildroot}目录。%{buildroot}目录相当于安装系统的根目录。
- %files需要打包的文件列表,注意这里的文件路径是以${buildroot}制定的安装后的根目录。可以使用通配符,如/etc/keepalived/*。注意:使用通配符时,如果安装目录有文件与安装包中冲突,安装失败。
- %changelog(没有使用) 软件的变化记录。
- scriptlets 安装卸载时执行脚本(没有使用)
- 在软体包安装之前 (%pre) 或之后 (%post) 执行
- 在软体包卸载之前 (%preun) 或之后 (%postun) 执行
- 在事务开始 (%pretrans) 或结束 (%posttrans) 时执行
制作包
执行rpm-build -bb hello.spec
生成二进制rpm包,默认情况下会在当前用户生成目录~/rpmbuild
1 |
|
建议定义_topdir宏,将安装包生成到制定目录。
下面的命令生成rpm包到当前目录:
1 |
|
自定义rpm包名称
默认安装包名称定义在/usr/lib/rpm/macros文件中:
1 |
|
在hello.spec的第一行修改%_rpmfilename宏进行自定义包名称安装
1 |
|
再次执行打包命令,查看rpmbuild/RPMS目录生成自定义rpm包。
1 |
|
自启动
生成systemd自启动服务需要编写hello.service文件,本文并不做hello.service文件格式的具体说明,只是说明打包流程。通过修改hello.spec文件就可以将hello.service添加到systemd自启动服务中:
- %install阶段生成/etc/systemd/system/hello.service
- %post阶段在安装后启动hello.service服务
- %preun阶段在卸载前停止并禁用hello.service服务
hello.service文件如下:
1 |
|
修改后的hello.spec文件如下
1 |
|
安装生成的rpm包,并查看hello.service服务
1 |
|
卸载hello包,执行了停止和移除服务命令
1 |
|
可能的错误
执行rpmbuild脚本报错:rpmbuild contains an invalid rpath error 0002
rpmbuild在打包时会检查可执行文件的rpath动态链接在本机上是否可用,但是可执行文件在本机上可能没有安装。
一种解决方法时跳过rpath检测,报错信息中也给出了提示:
- 跳过所有rpath检测:QA_SKIP_RPATHS=1 rpmbuild -bb hello.spec
- 跳过不安全和空的rpath链接:QA_RPATHS=$(( 0x0002|0x0010 )) rpmbuild my-package.src.rpm
pkg.tar.zst
此格式是Arch/Manjaro Linux特有的软件包格式。
此格式前一个版本是pkg.tar.xz,后来逐步替换为pkg.tar.zst
Arch Linux 中的软件包是通过 makepkg 工具以及存储在 PKGBUILD 文件中的信息编译的。运行makepkg时,系统将自动在当前目录下搜索 PKGBUILD文件,然后根据PKGBUILD把软件源码重新打包。成功编译后得到的二进制文件和可以得到的其他信息如包的版本信息和依赖关系等,都将被打包到一个文件叫name.pkg.tar.zst 里,可以通过pacman -Up <package file>
进行安装。
一个 Arch 软件包仅仅是一个使用 zstd(1) 压缩的 tar 压缩包,或者叫 ‘tarball’。它包含了以下由 makepkg 生成的文件:
- 要安装的二进制文件
- .PKGINFO: 包含所有 pacman 处理软件包的元数据,依赖等等。
- .BUILDINFO: 包含可复现编译需要的信息,仅在 pacman 5.1 及之后编译的软件包中。请参阅 BUILDINFO(5).
- .MTREE: 包含了文件的哈希值与时间戳. pacman 能够根据这些储存在本地数据库的信息校验软件包的完整性.
- .INSTALL: 可选的文件,可以用来在安装/升级/删除操作之后运行命令。(本文件只有在 PKGBUILD 中制定才会存在。)
- .Changelog: 一个可选的文件,保存了包管理员描述软件更新的日志。(不是所有包中都存在。)
创建PKGBUILD
当你运行makepkg时,它会在当前工作目录寻找一个PKGBUILD文件。如果找到PKGBUILD文件,它会下载该软件的源代码,根据PKGBUILD文件中的指令编译它。PKGBUILD中的指令必须能完全被Bash解释。成功完成后,最后的二进制文件和包的元信息(即包的版本、依赖)被一起打包在pkgname.pkg.tar.zst文件包中,这个文件包可以使用pacman -U
要开始制作一个包,你应该先创建一个空工作目录,进入该目录,创建一个PKGBUILD文件。你可以复制PKGBUILD模板(位于/usr/share/pacman/)到工作目录,或者复制一个类似包的PKGBUILD也可以。如果你只想在别人的基础上更改一些选项的话,后一种方法比较方便。
定义PKGBUILD变量
PKGBUILD文件的编写例子可以在/usr/share/pacman/处找到。PKGBUILD文件中可能用到的一些变量意义的解释可以在PKGBUILD中找到。
makepkg 定义了两个变量,你应该在编译和安装的过程中使用它们:
- srcdir makepkg将会把源文件解压到此文件夹或在此文件夹中生成指向 PKGBUILD 里 source 数组中文件的软连接。
- pkgdir makepkg会把该文件夹当成系统根目录,并将软件安装在此文件夹下。
这些变量都是绝对路径, 即意味着, 如果你合适地使用这些变量, 就不用担心当前工作目录的影响.
注意: build()和package()函数在运行过程中都应当是非交互的。在这些函数中调用交互工具或脚本可能会中断makepkg的运行。(参考FS#13214)
注意: 如果你是接手别人的包,除了把你的名字列为包维护者(Maintainers)外,你还应当把之前的维护者列为贡献者(Contributors)。
PKGBUILD 函数
一共有五个函数, 以下按照它们执行的先后顺序列出。package() 函数是每个 PKGBUILD 中必须的函数,其余不存在的函数可以跳过。
prepare()
此函数会执行用于预处理源文件以进行构建的命令, 例如 patching. 此函数执行在 build() 之前, 软件包解压之后. 如果解压过程被跳过 (makepkg -e), 那么 prepare() 函数就不会被执行.
注意: (从 PKGBUILD(5)) 中可以知道, 该函数运行在 bash -e 模式下, 意味着任何以非零状态退出的命令都会造成该函数中止.
pkgver()
pkgver() 会在抓取并解压源文件,执行 prepare() 后后执行此函数。
如果你正在制作 git/svn/hg 等构建过程相同, 但源文件可能每天甚至每小时更新一次的软件包的时候, 这一特性是十分有用的. 过去的方法是把日期写入到 pkgver 变量中, 但这样一来 makepkg 会在即使软件没有更新的情况下依然重新构建软件包, 因为它会认为软件包的版本改变了. 其他与此有关的命令有 git describe, hg identify -ni 等等. 请在提交 PKGBUILD 前做好测试, 因为如果 pkgver() 执行失败, 整个构建过程都会终止.
注意: pkgver 不能含有空格或连接符 (-). 通常都会用 sed 来进行修改.
build()
现在你需要编写PKGBUILD文件中的build()函数。这个函数使用通用的shell命令来自动编译软件并创建软件的安装目录。这允许makepkg无需详查你的文件系统就可以打包你的软件。
在build()函数中第一步就是进入由解压源码包所生成的目录。 makepkg 会在执行 build() 函数之前更改当前目录为 $srcdir; 因此, 大多数情况下第一条命令是这样的(参考示例文件/usr/share/pacman/PKGBUILD.proto):
1 |
|
现在,你需要把你当时手动编译软件时用到的命令一一列上。build()基本上会自动运行你当时手动输入的命令并在伪root环境下编译该软件。如果你要打包的软件使用了一个配置脚本,最好在配置中加上–prefix=/usr。许多软件都将自己安装到/usr/local下,我们仅仅推荐当你手动从源码安装时这么做。所有的Arch Linux软件包都应当使用/usr目录。
1 |
|
注意: 如果你的软件不需要构建任何东西, 请不要使用 build() 函数. 但package() 函数依然是必须的.
check()
用来执行make check和其他一些例行测试的地方。如果不需要可以通过在 PKGBUILD/makepkg.conf 中使用 BUILDENV+=(’!check’) 或者给 makepkg 传入参数 --nocheck 来禁用它。
package()
最后一步就是把编译好的文件放到pkg文件夹——一个简单的伪root环境。pkg目录复制了根目录下软件安装路径的继承关系。如果你需要手动把文件放到根目录下,那么在这里你需要把文件放在pkg下相同的文件层级结构中。比如,你想把一个文件安装到/usr/bin,那么在伪root环境中对应的路径为$pkgdir/usr/bin。极少情况下的安装步骤需要用户手动复制大量的文件到某个地方。大部分软件安装时只需要调用make install即可。为了将软件安装到正确的路径,最后一行一般应该这样写:
1 |
|
注意: 有时候在Makefile里没有使用DESTDIR;你可能需要使用prefix来替代。如果软件包是用autoconf/automake来创建的,那就使用DESTDIR;如果DESTDIR不起作用,试试make prefix="$pkgdir/usr/" install。如果这还不起作用的话,你就需要深入检查软件的安装命令了。
makepkg --repackage 命令只运行package()函数,它只是将文件打包成软件包,并不运行编译过程。如果你只是更改了PKGBUILD中的依赖,用这个命令来打包可以节省很多时间。
测试PKGBUILD文件
你在写PKGBUILD的 build()方法时,会想频繁的测试你所做的改动以确保没有bug。你可以在包含 PKGBUILD的目录下运行makepkg命令来确保没有问题。如果PKGBUILD没有错误,将会生成一个包,但是如果PKGBUILD被破坏或未完成,它将抛出一个错误。
如果运行makepkg 成功,在你工作的目录下将会生成一个名为pkgver.pkg.tar.gz的新文件。这个文件可以使用pacman -U 或 pacman -A安装,你也可以将它加到本地或网上的软件仓库中。注意,一个包被构建并不代表你的工作就完成了!只有当所有文件的结构都正确才能确保完成,例如你给了一个不正确的前缀就不行。你可以使用pacman的查询功能显示软件包包含的文件及依赖的文件,然后将它于你认为正确的对比。"pacman -Qlp
如果包看起来是正确的,那你的工作就完成了。但是如果你打算发布这个包或PKGBUILD,你就需要确认确认再确认包的依赖关系。
同样要确保安装的软件确实很完美的运行!如果你释放了一个包括所有必需文件的包,但是由于一些配置选项使它不能很好的工作,这真是让人恼火。如果你只是为你自己的系统安装这个软件,你就不必做这个质量保证了,因为只有你一个人需要忍受这些错误。
检查包的逻辑性
确定包可以正常使用后,再使用namcap来检查错误:
1 |
|
Namcap将会做以下工作:
- 检查PKGBUILD文件里的一些常见错误
- 用ldd扫描包中所有的ELF文件,自动报告缺失或可去除的依赖。
- 启发式搜寻缺失或冗余的依赖。
要养成用namcap检查包的习惯,以避免提交包后再做修复的麻烦。
run
此格式是与发行版无关的
run格式是由执行脚本和可执行文件压缩包组成。压缩包通过数据的形式加在脚本后面,执行时先把压缩包导出来,然后继续执行脚本直到结束。
那么打包run格式有三个步骤:
(1)写脚本
(2)压缩可执行文件
(3)拼接脚本和压缩包
(4)执行
写脚本
示例脚本
1 |
|
将上面的脚本保存为.sh格式文件,其中末尾要有一个空行用于添加压缩包。
那么本次测试软件的脚本为:
1 |
|
$0表示文件自身
tail -n +12 表示将本run文件的从第12行起复制到/tmp/cal.tar.xz文件中
xz命令为解压xz压缩包,得到tar格式压缩包
tar命令为解压tar格式压缩包,并将结果导出到-C 指定的文件夹中,文件夹需要先创建
将工作目录转移到解压后的文件夹,并执行
执行完成后删除所有临时文件
退出
压缩文件
将所有的可执行文件及配置文件压缩为.tar.xz文件
文件名要和脚本中的文件名相同
拼接文件
1 |
|
就会生成.run格式文件,然后赋予可执行权限就可以了。
执行后,会把压缩包的文件全部解压到临时文件夹中,然后执行程序。软件退出后会删除所有的临时文件。
此格式优点是执行方便,缺点就是每次执行都要解压文件,如果文件很大会很耗时。
appimage
此格式是与发行版无关的
打包工具
先下载打包软件:https://github.com/AppImage/AppImageKit/releases
linuxdeploy
打包
在含有这两个的⽂件的终端中输⼊:
1 |
|
该命令⽣成⼀个叫APPDIR的⽂件夹。
- –appdir= ⽣成的⽂件名,
- -e 后接可执⾏⽂件,
- -l 后接该打包系统在其他系统执⾏过程中所缺的⽂件(⼀般不⽤)
- –create-desktop-file ⽣成桌⾯⽂件,不知道为什么⼀定要加。
- –icon-file=FastFusionV2.png 给桌⾯⽂件加上⼀个图标, (也是第⼀次⽤的时候必须操作)
运行
在终端中输⼊
1 |
|
说明:该步的输⼊参数是上⼀步中⽣成的⽂件,然后会输出⼀个可执⾏的打包⽂件和第⼀步中加⼊的可执⾏⽂件是同名的(第⼀次⽤⼀个要赋予相应的权限)
snap
此格式是与发行版无关的
依赖
1 |
|
打包
切换至包目录输入 snapcraft init 执行结束后返回
1 |
|
此时文件目录中会生成snap文件夹,文件夹下存在文件snapcraft.yaml,以下是生成的snapcraft.yaml文件内容
1 |
|
- snapcraft.yaml 与debian中的control文件相似,以下是文件中部部分参数解析,*标记标识为必填项
- name * snap包的名字,必须以ASCII字符开头,只能包含小写字母、数字和连字符,不能以连字符开头或结尾。如果您想发布到 Snap Store,该名称必须是唯一的
- type snap 的类型,如果未设置则隐式设置为app,常见设置为kernel、base 、gadget
- base * 要用作此快照的执行环境的base类型快照 如 core core18 core20 当type类型为kernel、base 或者gadget时,可以不填写此项
- version *· 当前snap包的版本
- summary * 不超过79个字的摘要
- description * 不超过100个字的sanp包简介
- grade snap包的质量等级,stable或者devel。如果需要在snap store的stable channel中发布snap包,需要设置成stable
- confinement 确定是否应限制快照的访问
- parts 描述snap包中代码如何获取、依赖关系和如何编译等
- my-part 当前parts名称,由snapcraft引用该名称
- plugin 构建过程的插件
- source 要构建的源树的 URL 或路径,这可以是本地路径或远程路径,并且可以引用目录树、压缩存档或修订控制存储库
- build-packages 构建snap包的编译依赖,书写格式如下[ libssl-dev, libssh-dev, libncursesw5-dev]
- stage-packages 安装snap包的执行依赖,书写格式如下[ python-zope.interface, python-bcrypt]
flatpak
此格式是与发行版无关的
依赖
flatpak要求每个应用程序都指定一个运行时,用于其基本依赖关系。每个运行时都有一个匹配的sdk(软件开发工具包),其中包含运行时的所有内容,以及头文件和开发工具(类似于Linux发行版中的-devel / -dev软件包)。这个sdk是为运行时构建应用程序所必需的。
在本教程中,我们将使用freedesktop运行时版本1.6。此运行时由flathub存储库提供。运行:
1 |
|
然后,要安装运行时和sdk,请运行:
1 |
|
程序
将为本教程创建的应用程序是一个简单的脚本。要创建它,请将以下内容复制到一个空文件并将其另存为hello.sh
:
1 |
|
manifest
大多数flatpaks是使用flatpak-builder工具构建的。这会读取一个清单文件,它描述应用程序的关键属性以及如何构建它。
要向hello world应用添加清单,请将以下内容添加到空文件中:
1 |
|
现在将文件保存在hello.sh同目录中并将其命名为为org.flatpak.hello.json。
在更复杂的应用程序中,manifest会列出多个模块。最后一个通常是应用程序本身,而较早的将是与应用程序捆绑在一起的依赖项,因为它们不是运行时的一部分。
构建
现在应用程序有一个manifest,flatpak-builder可以用来构建它。这是通过指定manifest文件和目标目录完成的:
1 |
|
此命令将构建manifest中列出的每个模块,并将其安装到app-dir目录内的/app子目录中。
测试
要验证构建是否成功,可以运行以下内容:
1 |
|
库
在安装和运行应用程序之前,首先需要将其存储在存储库中。这是通过将-repo参数传递给flatpak-builder完成的:
1 |
|
这会再次执行构建,并在最后将结果导出到称为repo的本地目录。请注意,flatpak-builder会在.flatpak-builder子目录中保留先前构建的缓存,因此,这样做第二个构建速度非常快。
这是我们第二次在-force-clean中传递,这意味着之前创建的app-dir目录在新版本开始之前被删除。
安装
现在我们准备添加刚创建的库并安装应用程序。这是用两个命令完成的:
1 |
|
第一个命令添加上一步中创建的存储库。第二个命令从存储库安装应用程序。
这两个命令都使用-user参数,这意味着存储库和应用程序是按用户而不是系统范围添加的。这对测试很有用。
请注意,存储库中添加了-no-gpg-verify,因为在构建应用程序时未指定GPG密钥。这对测试来说很好,但对于官方软件仓库,您应该使用私人GPG密钥对其进行签名。
测试
剩下的就是尝试应用程序。这可以通过以下命令完成:
1 |
|
tar.gz
这种类型的包,是开发者直接将软件压缩后的文件,解压就可以使用,但是可能会出现依赖问题。