RPM 打包

本文是以 RPM Packaging Guide 为主线,进行简化、翻译,中间根据个人的学习需要增减了一些内容,掺入其他很多来源的资料改编而成。应该适合对 linux 打包没什么概念的同学(比如我)阅读

一些基础概念

软件补丁 ( Patch )

软件补丁,是用来修改其他代码的代码。我们使用 diff 工具来创建补丁,并借助 patch 工具来为源代码打补丁。

注意,在打包时对原始的软件代码的修改要以 patch 的形式来做,而不能直接修改代码。RPM 包中的程序源代码需要与代码作者提供的源代码一致

整挺好,那么我们要怎么来制作补丁呢?

  1. 首先,备份要修改的代码

    $ cp ./cello.c ./cello.c.orig
    复制代码
    

    原始代码长这样:

    #include 
    
    int main(void) 
    {
        printf("Cello! Wtf!\n");
        return 0;
    }
    复制代码
    
  2. cello.c 的代码进行修改

    #include 
    
    int main(void) 
    {
        printf("I am the patched Cello!\n");    // 只改了这里
        return 0;
    }
    复制代码
    
  3. 使用 diff 输出 patch

    $ diff -Naur cello.c.orig cello.c > cello-output-first-patch.patch
    复制代码
    

    于是,我们得到了这样的一个 patch 文件

  4. 然后再用之前备份的原始代码,替换掉我们修改过的代码

    $ rm cello.c
    $ mv cello.c.orig cello.c
    复制代码
    

patch 的使用

在原始代码和 patch 所在的文件夹中,执行命令:

$ patch < cello-output-first-patch.patch # 把 .patch 文件作为 patch 软件的输入
复制代码

再打开目录下的 cello.c 文件,就会发现该代码已经被修改好了

安装( install )

安装,是把 RPM 包中的东西【放到其应该在的地方】并【调整好用户对这些东西的权限】的过程。比如可执行文件要放在 PATH 的目录中并且要让所有者可执行,用于开发的头文件要放在包含目录中,库文件要放在库文件该在的地方等。

安装有两种方式,一种是我们可以使用系统自带的 install 工具来把包里的文件放到文件系统中的指定位置,并根据需要设置用户对被安装文件所拥有的权限;另一种是在用 make 进行构建时使用 make install

install

示例操作如下:

$ gcc -o cello cello.c  # 编译 cello
$ sudo install -m 0755 cello /usr/bin/cello # 安装 cello 到 /usr/bin ,权限 0755
复制代码

如此一来,在任何目录都可以直接使用 cello 来调用我们的程序了

make install

make install 的具体设置需要在 makefile 文件中进行配置。

cello:
    gcc -g -o cello cello.c

clean:
    rm cello

install:
    mkdir -p $(DESTDIR)/usr/bin
    install -m 0755 cello $(DESTDIR)/usr/bin/cello
复制代码

$(DESTDIR) 是用来在我们不想把东西装到根目录的时候,指定目录前缀用的。比如,如果想装到当前目录下,可以使用如下命令:

$ make      # 编译
$ sudo make install DESTDIR="./"  # 编译出的文件会装到 ./usr/bin/
复制代码

这样,二进制文件就会被装到带前缀的目录下。如果想装在根目录下,省略对 DESTDIR 的指定即可:

$ make
$ sudo make install # 编译出的文件会装到 /usr/bin/
复制代码

准备代码

可以使用原文作者提供的代码:rpm-packaging-guide/example-code

注意:每一份代码都要和一个 LICENSE 文件放在一起,装到一个文件夹中(文件夹命名需要是%{name}-%{version},如 cello-1.0 ),再将这个文件夹压缩打包为 cello-1.0.tar.gz ,放在 ~/rpmbuild/SOURCES/ 中。

我们要将 .tar.gz 文件和 .patch 一起全部放入 ~/rpmbuild/SOURCES/

打包

创建 RPM 包的流程:

  1. ~/rpmbuild/SPECS/ 创建 .spec 文件,命名为 “软件包名.spec” ,并在其中配置打包信息。我们可以使用 rpmdev-newspec 工具来从模板自动生成一个 spec
```
$ rpmdev-newspec myfirstpackage.spec
复制代码
```



样例以及各个标签的解释说明,见文末附录
  1. 将源代码包放入 ~/rpmbuild/SOURCES/
  1. 切换到 ~/rpmbuild/SPECS/ ,对 .spec 文件执行 rpmbuild 命令:
```
$ rpmbuild -ba NAME.spec  # -ba: 根据 NAME.spec 构建源代码和二进制软件包
复制代码
```

关于 SRPM 包和 RPM 包:SRPM 包( source RPM )中包含源代码、补丁和 SPEC 文件(描述如何用 SRPM 包中的文件构建 RPM 包);RPM 包(binary RPM)则只包含编译好的二进制文件。

建立目录树和 SPEC 文件

注意,不要使用 root 用户进行打包操作。在家目录下执行:

$ rpmdev-setuptree
复制代码

该命令会建立 npmbuild 目录。

~/rpmbuild/SPECS/中,执行:

$ rpmdev-newspec cello.spec
复制代码

该命令会生成一个空的 spec 文件: cello.spec

然后将源代码压缩包 cello-1.0.tar.gz 和补丁cello-output-first-patch.patch 一起放入 ~/rpmbuild/SOURCES/

准备工作基本完成,接下来我们开始写 SPEC 文件

/etc/rpmdevtools/ 中含有多种内置的 spec 模板

rpmbuild 目录树简介

  • BUILD:构建包时将会在此创建多个不同的 %buildroot 目录。当构建失败时,可以到这里来查找构建失败的原因
  • RPMS:保存生成的 binary RPM
  • SOURCES:存放代码压缩包和补丁,供 rpmbuild 命令使用
  • SPECS:存放 SPEC 文件
  • SRPMS:保存生成的 source RPM

编写 SPEC

注:这里会涉及到一些 RPM 内置宏,部分见文末附录
注 2 :附录中还有一个 Fedora 16 eject 的 spec 作为示例参考,写了不少注释

.spec 是干嘛的呢?

.spec 文件定义了该如何构建、安装该软件包中的软件。

它说明了:

  • 该软件包是哪来的、什么版本、代码放在哪里、有哪些补丁、有什么依赖
  • 系统下载了该软件包后,该怎么处理源代码(比如把代码从压缩包中解压出来、打补丁等)、该如何编译构建软件、在构建完成后该怎么安装(比如把二进制文件复制到 /usr/local)、

打开刚刚自动生成的 cello.spec

  • 会发现最上面的 Name 已经被用 ”cello“ 填好了——该名称需要与 spec 文件名相同,所以已经被 spec 创建的命令自动填好了

    Name:           cello
    复制代码
    
  • Version 值需要与上游代码的版本号一致,此处为 1.0

    Version:        1.0
    复制代码
    
  • Release 是发行版号,根据我们自己发布该包的版本来写,每次进行新发布要增加 1。当需要跟着上游更新代码时,要重置为 1

    Release:        1
    # 也可以使用宏来实现打包时 Release 自增,写法如下
    # Release:        1%{?dist}
    复制代码
    
  • Summary 是简介,要用简洁的语言对包进行描述,结尾不要有标点。如果是来自 github 的项目,Summary 可以直接用 github 项目中的 description

    Summary:        Hello World example implemented in C
    复制代码
    
  • License 要根据实际情况来填写。需要注意填写简称

  • URL 填写项目网址。 Source0 最好填写可以获取到包中源码的 url

  • 如果包中有多个源码,则编号递增。Patch 处理方式相同

  • BuildRequires 填写编译时依赖

    BuildRequires:  gcc
    BuildRequires:  make
    复制代码
    
  • 这里省略了 Requires 。该处应填写运行时依赖。本包中的程序非常简单,不需要 C 运行时之外的东西,所以省略

接下来填写带百分号的这些 directive 值。这些 directive 后面的内容可以换行,可以包含 bash 脚本等

  • %description 可以多写点,但最好不要写太多,一段即可

  • %prep 的意思是 prepare:该部分可用于准备构建和编译的环境。一般来说这里进行的操作,都是解压代码压缩包、给解压好的代码打补丁、为接下来的操作做准备等。这里只用一个宏

    %prep
    %setup -q
    复制代码
    
  • %build 部分描述了具体应该如何从源码开始构建程序,所以用 rpmdev 为 GNU make 提供的宏即可

    %build
    make %{?_smp_mflags}
    # 上面这个其实就是 "make"
    复制代码
    
  • %configure 用于填写配置脚本( configure script )。此处不涉及,所以这里省略

  • %install 用于告诉 rpmbuild 应如何把刚刚构建好的软件安装到 BUILDROOT 中。BUILDROOT 是一个 chroot 根目录,我们可以在里面根据需要建立任何我们想要的路径或目录结构,实现为我们的软件指定其在最终用户的客户机上安装的位置。比如,我们一般会指定把二进制可执行文件放到 /usr/bin/ 中。

    BUILDROOT 见附录,有另一段介绍

    然而,我们这个 cello 小程序是使用 make 进行安装的,因此我们可以使用 rpmdev 宏来指定使用 make 进行安装操作,而不在这个 spec 文件中再次指明,示例如下:

    %install
    %make_install
    复制代码
    
  • %files 部分是我们希望本 RPM 包中的、最终会被装到用户电脑上的文件的列表,以及这些文件应该被放在哪里。注意,这里与 %{buildroot} 无关,这里要写的应该是我们最终希望的、我们的 RPM 包中文件被放置在用户电脑上的完整路径。因此,将 cello 可执行文件在这里写为:

    %files
    %license LICENSE
    %{_bindir}/%{name}
    复制代码
    
  • %changelog 应重点体现软件包 release 的变动,如对软件打了新的补丁、构建过程变动等。每个版本变动可以包含多个变动条目,每个条目以一个 - 开头,如:

    %changelog
    * Tue May 31 2016 Adam Miller  - 0.1-1
    - First cello package
    复制代码
    

完成的 spec 文件如下:

Name:           cello
Version:        1.0
Release:        1%{?dist}
Summary:        Hello World example implemented in C

License:        GPLv3+
URL:            https://www.example.com/%{name}
Source0:        https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz

Patch0:         cello-output-first-patch.patch

BuildRequires:  gcc
BuildRequires:  make

%description
The long-tail description for our Hello World Example implemented in
C.

%prep
%setup -q

%patch0

%build
make %{?_smp_mflags}

%install
%make_install

%files
%license LICENSE
%{_bindir}/%{name}

%changelog
* Tue May 31 2016 Adam Miller  - 1.0-1
- First cello package
复制代码

附录

标准 RPM 内置宏

来自:Built-in macros

%_prefix /usr 
%_exec_prefix %{_prefix} 
%_bindir %{_exec_prefix}/bin 
%_sbindir %{_exec_prefix}/sbin 
%_libexecdir %{_exec_prefix}/libexec 
%_datadir %{_prefix}/share 
%_sysconfdir %{_prefix}/etc 
%_sharedstatedir %{_prefix}/com 
%_localstatedir %{_prefix}/var 
%_libdir %{_exec_prefix}/lib 
%_includedir %{_prefix}/include 
%_oldincludedir /usr/include 
%_infodir %{_prefix}/info 
%_mandir %{_prefix}/man 
复制代码

除了标准宏之外,不同的发行版(很)可能会有自己的宏

Fedora 对于 spec 各个 section 以及部分宏的介绍

来自:How to create an RPM package

当执行此命令时,rpmbuild 会自动读取 .spec 文件并按照下表列出的步骤完成构建。下表中,以 % 开头的语句为预定义宏,每个宏的作用如下:

阶段 读取的目录 写入的目录 具体动作
%prep %_sourcedir %_builddir 读取位于 %_sourcedir 目录的源代码和 patch 。之后,解压源代码至 %_builddir 的子目录并应用所有 patch。
%build %_builddir %_builddir 编译位于 %_builddir 构建目录下的文件。通过执行类似 "./configure && make" 的命令实现。
%install %_builddir %_buildrootdir 读取位于 %_builddir 构建目录下的文件并将其安装至 %_buildrootdir 目录。这些文件就是用户安装 RPM 后,最终得到的文件。注意一个奇怪的地方: 最终安装目录 不是 构建目录。通过执行类似 "make install" 的命令实现。
%check %_builddir %_builddir 检查软件是否正常运行。通过执行类似 "make test" 的命令实现。很多软件包都不需要此步。
bin %_buildrootdir %_rpmdir 读取位于 %_buildrootdir 最终安装目录下的文件,以便最终在 %_rpmdir 目录下创建 RPM 包。在该目录下,不同架构的 RPM 包会分别保存至不同子目录, "noarch" 目录保存适用于所有架构的 RPM 包。这些 RPM 文件就是用户最终安装的 RPM 包。
src %_sourcedir %_srcrpmdir 创建源码 RPM 包(简称 SRPM,以.src.rpm 作为后缀名),并保存至 %_srcrpmdir 目录。SRPM 包通常用于审核和升级软件包。

rpmbuild 中,对上表中的每个宏代码都有对应的目录:

宏代码 名称 默认位置 用途
%_specdir Spec 文件目录 ~/rpmbuild/SPECS 保存 RPM 包配置(.spec)文件
%_sourcedir 源代码目录 ~/rpmbuild/SOURCES 保存源码包(如 .tar 包)和所有 patch 补丁
%_builddir 构建目录 ~/rpmbuild/BUILD 源码包被解压至此,并在该目录的子目录完成编译
%_buildrootdir 最终安装目录 ~/rpmbuild/BUILDROOT 保存 %install 阶段安装的文件
%_rpmdir 标准 RPM 包目录 ~/rpmbuild/RPMS 生成/保存二进制 RPM 包
%_srcrpmdir 源代码 RPM 包目录 ~/rpmbuild/SRPMS 生成/保存源码 RPM 包(SRPM)

如果某一阶段失败,请查看输出信息以了解失败原因,并根据需要修改 .spec 文件。

Fedora 16 eject 程序的 spec 文件

来自:How to create an RPM package

# 如果是来自 github 的项目,Summary 可以直接用 github 项目中的 description ,结尾不要有标点
Summary:            A program that ejects removable media using software control
# Name 要与 spec 文件名相同
Name:               eject
# Version 要填写上游的版本号,也就是此代码拥有者定义的版本号
Version:            2.1.5
# Release 是发行版号,根据我们自己发布该包的版本来写,每次发布递增 1。跟着上游更新时,要重置为 1
Release:            21%{?dist}
# 简写,参考:https://fedoraproject.org/wiki/Licensing:Main?rd=Licensing
License:            GPLv2+
# 指定软件包组
Group:              System Environment/Base
# 最好使用 URL 地址。若只有一个源码包,则 Source 和 Source0 等价。若有多个,则从 0 往后排
# 例:Source0: https://downloads.sourceforge.net/%{name}/%{name}-%{version}.tar.gz
Source:             %{name}-%{version}.tar.gz
# 源码补丁名称。补丁也放在 ~/rpmbuild/SOURCES 目录中
# 若要在源码包解压后修改代码,应修改代码并使用 diff 生成 patch 文件
Patch1:             eject-2.1.1-verbose.patch
# 一个补丁只做一种修改,做多种修改需要拆分补丁
Patch2:             eject-timeout.patch
Patch3:             eject-2.1.5-opendevice.patch
Patch4:             eject-2.1.5-spaces.patch
Patch5:             eject-2.1.5-lock.patch
Patch6:             eject-2.1.5-umount.patch
# 软件包项目主页。若代码来自 github 且没有官方主页,则放该项目的 github 主页地址
URL:                http://www.pobox.com/~tranter
# ExcludeArch:排除某些架构;ExclusiveArch:独占某些架构。
#              若代码与架构无关,则用 BuildArch: noarch 替代掉下面的
ExcludeArch:        s390 s390x
# 编译依赖不会自动列出,所以在下面给出需要的依赖包列表。可以指定版本,写法如 ocaml >= 3.08
BuildRequires:      gettext
BuildRequires:      libtool
# 本例中没有的标签:
# Requires:安装和运行时所需的依赖包列表,以逗号分隔。一般 rpmbuild 会自动探测安装运行的依赖,
#           可以明示写出,也可以在无法自动检测的时候写出来
# BuildRoot:在 %install 阶段(%build 阶段后)文件需要安装至此位置
#           默认根目录为 "%{_topdir}/BUILDROOT/"

# 简短描述。如果是 Github 项目,可以直接复制项目 README 最开头的简介
%description
The eject program allows the user to eject removable media (typically
CD-ROMs, floppy disks or Iomega Jaz or Zip disks) using software
control. Eject can also control some multi-disk CD changers and even
some devices' auto-eject features.

Install eject if you'd like to eject removable media using software
control.

# 打包准备阶段执行一些命令(如,解压源码包,打补丁等),以便开始编译。一般仅包含 "%autosetup"
%prep
# 仅解压源码包至 %{name} 目录
%autosetup -n %{name}
# 若解压后包含多个目录,使用 : %autosetup -c %{name},都解压到 %{name} 目录中
# 如果要解压多个文件,还有更多选项可用。详见: 
# https://fedoraproject.org/wiki/How_to_create_an_RPM_package/zh-cn

%build
# 此处可以写构建所需的脚本命令,比如调用某个 .sh 、执行一句 make && make install
%configure
make %{?_smp_mflags}

# 包含安装阶段的脚本命令,比如把编译好的文件从 `%builddir` 拷贝到 `%buildroot`
# %builddir(BUILD)是解压、编译、生成二进制文件的目录
# %buildroot(BUILDROOT)是存放编译好的文件以及目录结构的目录
%install
%make_install

install -m 755 -d %{buildroot}/%{_sbindir}
ln -s ../bin/eject %{buildroot}/%{_sbindir}

%find_lang %{name}

# 需要被安装到用户操作系统中的文件列表
%files -f %{name}.lang

%doc README TODO COPYING ChangeLog
%{_bindir}/*
%{_sbindir}/*
%{_mandir}/man1/*

# RPM 包变更日志。注意不是软件本身的变更日志
%changelog
* Tue Feb 08 2011 Fedora Release Engineering  - 2.1.5-21
- Rebuilt for https://fedoraproject.org/wiki/Fedora_15_Mass_Rebuild

* Fri Jul 02 2010 Kamil Dudka  2.1.5-20
- handle multi-partition devices with spaces in mount points properly (#608502)
复制代码

BUILDROOT 介绍

The parameter names are largely self-evident, but BuildRoot merits some explanation to differentiate it from the BUILD directory you already created. BuildRoot is a proxy for the final installation directory. In other words, if wget is ultimately installed in /usr/local/bin/wget and other subdirectories in /usr/local, such as /usr/local/man for documentation, BuildRoot stands in for /usr/local during the RPM build process. Once you set BuildRoot, you can access its value using the RPM_BUILD_ROOT environment variable. You should always set BuildRoot in your spec file and check the contents of that directory to verify what is going to be installed by the package.

developer.ibm.com/tutorials/l…

你可能感兴趣的:(RPM 打包)