什么是 RPM
RPM 全稱為:Red-Hat Package Manager,即紅帽 linux 發行版的軟件包管理器。RPM 的出現,提升了 Linux 軟件安裝、升級的便捷性。RPM 遵循 GPL 協議,除了紅帽 Linux 發行版,Caldera OpenLinux、SUSE 以及 Turbo Linux 等 Linux 的發行版也使用 RPM,因此 RPM 是 Linux 軟件包管理的行業標準。為了使讀者能夠較為深入理解 RPM,我們先介紹軟件的構建方法。
計算機軟件的軟件是從源代碼構建出來的。源代碼是人們以人類可讀的語言書寫的、讓計算機執行任務的指令。人類可讀的語言格式和規范,就是編程語言。
從源代碼制作軟件的過程,稱之為是軟件編譯。從源代碼構建成軟件的編譯有兩種方式:
- 本機編譯 (Natively Compiled),對應編譯型語言。
- 解釋編譯(Interpreted Compiled),對應解釋性語言。
本機編譯方式下,代碼可以獨立編譯成機器代碼或直接編譯為二進制文件可執行文件。本機編譯構建的軟件包中,包含編譯環境下計算機體系架構的特征。例如,使用 64 位(x86_64)AMD 計算機中編譯的軟件,不能在 Intel 處理器架構上運行。
與本機編譯可以獨立執行相對應,某些編程語言不能將軟件編譯成計算機可以直接理解的格式,而需要語言解釋器或語言虛擬機(如 JVM),我們稱之為解釋編譯。常用的解釋語言有 Byte Compiled(源代碼需要編譯成字節代碼,然后由語言虛擬機執行,如 Python)和 Raw Interpreted(原始解釋語言完全不需要編譯,它們由解釋器直接執行,如 Bash shell)兩種。
我們常用的 bash shell 和 Python 是解釋型的,這種方式編譯出的程序與硬件架構無關,通過這種方式編譯出的 RPM 會被標注為 noarch(說明 RPM 包不依賴于特定 linux 發行版)。
在介紹了源代碼的編譯方式后,接下來我們通過實驗的方式展現軟件的編譯過程。
從源代碼構建軟件
在正式開始驗證之前,我們需要在 Linux 中安裝編譯工具。
# yum install gcc rpm-build rpm-devel rpmlint make python bash coreutils diffutils
接下來,我們分別介紹本機編譯和解釋編譯。
本機編譯代碼
在編程語言中,C 語言是本機編譯。我們查看一個源代碼文件,如清單 1 所示:
清單 1. C 語言源碼文件
# cat cello.c
#include <stdio.h>
int main(void) {
printf("Hello World, I'm DavidWei!n");
return 0;
}
調用 C 編譯器 gcc 進行編譯:
# gcc -o cello cello.c
編譯成功后,我們可以執行結果輸出。
# ./cello
Hello World, I'm DavidWei!
為了實現自動化構建代碼,我們添加 Makefile,這是大型軟件開發中常用的方法。
首先創建一個 Makefile,如清單 2 所示:
清單 2. Makefile 文件
# cat Makefile
cello:
gcc -o cello cello.c
clean:
rm cello
接下來,通過 make 來完成編譯。
執行 make 會自動編譯源代碼,然后可以成功執行,如下圖 1 所示:
圖 1. 編譯并運行代碼
執行 make clean 會刪除編譯結果,如下圖 2 所示:圖 2. 刪除編譯結果
在介紹了本機編譯后,我們介紹解釋編譯。
解釋型代碼
對于用解釋型編程語言編寫的軟件,如果是 Byte Compiled 語言如 Python,就需要一個編譯步驟,把源代碼構建成 Python 的語言解釋器(稱為 CPython)的可以執行文件。
我們查看一個 python 的源代碼,如清單 3 所示:
清單 3. Python 源代碼文件
# cat pello.py
#!/usr/bin/env python
print("Hello World, I'm DavidWei!")
對源代碼進行編譯:
# python -m compileall pello.py
Compiling pello.py ...
編譯成功后運行:
# python pello.pyc
Hello World, I'm DavidWei!
我們看到,對源.py 文件進行字節編譯后會生成一個.pyc 文件,這是 python 2.7 字節編譯的文件類型,這個文件可以使用 python 語言虛擬機運行。
查看文件類型:
# file pello.pyc
pello.pyc: python 2.7 byte-compiled
和 python 相對應,無需編譯的解釋性代碼是 Raw Interpreted,如我們日常使用的 bash shell。
我們看一個 shell 文件,如清單 4 所示:
清單 4. Shell 文件
# cat bello
#!/bin/bash
printf "Hello World, I'm DavidWei!n"
對于 Raw Interpreted 源碼,我們使文件可執行、然后直接運行即可,如下圖 3 所示:
圖 3. 修改權限運行 shell
在介紹了如何從源碼構建軟件包后,接下來我們介紹如何給軟件打補丁。
給軟件打補丁
在計算機軟件中,補丁是用來修復代碼中的漏洞的。軟件中的補丁表示的是與源代碼之間的不同之處。接下來,我們從原始源代碼創建補丁,然后應用補丁。
創建補丁的第一步是備份原始源代碼,通常是將它拷貝為.orig 文件。我們以 cello.c 為例。
首先備份 cello.c,然后修改 cello.c 中的內容,如下圖 4 所示,我們修改了源代碼中的描述:
圖 4. 備份并修改源碼
首先查看兩個源碼文件的不同之處,如下圖 5 所示:
圖 5. 查看兩個源碼文件的不同
將兩個源碼的不同之處保存到
cello-output-first-patch.patch 中。
# diff -Naur cello.c.orig cello.c > cello-output-first-patch.patch
為了驗證打補丁的效果,將 cello.c 文件恢復為原始源代碼,如下圖 6 所示:
圖 6. 恢復 cello.c 初始內容
將補丁文件重定向到補丁,給源碼打補丁,如下圖 7 所示。
圖 7. 給源碼打補丁
從上圖 cat 命令的輸出中可以看到補丁已成功構建并運行,如圖 8 所示:
圖 8. 構建源碼并運行
至此,證明為打補丁成功。
安裝軟件
一旦我們構建了軟件,我們就可以將它放在系統的某個目錄下,以便用戶可以執行。為了方便操作,很多時候我們需要將編譯和安裝進行合并。
對于不需要編譯類的解釋型語言,例如 shell,可以使用 install 命令安裝到 linux 中,如下圖 9 所示:
圖 9. 安裝并執行 shell
對于需要編譯的語言,就需要先編譯再安裝。例如使用 make install。修改 makefile 文件,如下圖 10 所示:
圖 10. 修改 Makefile
構建并安裝 cello.c 程序,并執行驗證成功,如下圖 11 所示:
圖 11. 構建并安裝 cello.c
我們剛展示的是編譯與安裝在相同的環境下,即可以通過 Makefile 的方式,直接編譯和安裝程序;如果編譯和運行是兩個環境,那么我們就需要對軟件進行 RPM 打包。在 RPM 打包之前,需要將源代碼進行打包生成 tar.gz 文件。
源代碼生成 tar.gz 包
在源碼打包時,需要在每個源代碼版本中包含一個 LICENSE 文件。我們模擬生成遵守 GPLv3 的壓縮包,如下圖 12 所示:
圖 12. 生成 LICENSE 文件
將 bello 程序的源碼打包,如下圖 13 所示:
圖 13. 將 bello 程序的源碼打包
創建~/rpmbuild/SOURCES 目錄,將.tar.gz 文件移動過去,如下圖 14 所示:
圖 14. 移動 tar.gz 包
用相同的方法,我們為 Pello 和 Cello 的源碼打包,由于方法相同因此不再贅述。
將源碼打包以后,接下來我們就可以使用 RPM 將其構建成 RPM 包。
RPM 打包
RPM 文件有兩類:源 RPM(SRPM)和二進制 RPM。SRPM 中的有效負載是 SPEC 文件(描述如何構建二進制 RPM)。
查看 SRPM 的目錄結構,如下圖 15 所示:
圖 15. 查看 SRPM 目錄結構
上圖中 SRPM 五個目錄的作用分別是:
BUILD構建 RPM 包的時,會在此目錄下產生各種%buildroot 目錄。如果構建失敗,可以據此查看目錄日志,進行問題診斷。RPMS構建成功二進制 RPM 的存放目錄。存放在 architecture 的子目錄中。例如:noarch 和 x86_64SOURCES存放源代碼和補丁的目錄。構建 RPM 包時,rpmbuild 命令將會從這個目錄查找源代碼。SPEcssPEC 文件存放目錄SRPMS存放 SRPM 的目錄
在介紹了 SRPM 的目錄結構后,我們詳細介紹 SPEC 的作用。
什么是 SPEC 文件?
SPEC 文件是 rpmbuild 程序用于實際構建 RPM 的方法。SPEC 文件包含如下字段:
SPEC DirectiveDefinitionName包的名稱,應與 SPEC 文件名匹配Version軟件的上游版本號。ReleaseRPM 軟件版本號。初始值通常應為 1%{?dist},并在一個新版本構建時重置為 1.SummaryRPM 包的簡要說明License正在打包的軟件的許可證。URL該程序的更多信息的完整 URL(通常是打包的軟件的上游項目網站)。Source0上游源代碼的壓縮歸檔的路徑或 URL
如果需要,可以添加更多的 SourceX 指令,每次遞增數字,例如:Source1,Source2,Source3,依此類推Patch0應用于源代碼的第一個補丁的名稱。
如果需要,可以添加更多 PatchX 指令,增加每次編號如:Patch1,Patch2,Patch3 等。BuildArch表示 RPM 包的構建的計算機架構。如果包不依賴于體系結構,即完全用于編寫解釋的編程語言,這應該是 BuildArch:noarch。BuildRequires編譯軟件包所需的依賴包列表,以逗號分隔。Requires安裝軟件包時所需的依賴包列表,以逗號分隔。ExcludeArch如果某個軟件無法在特定處理器架構下運行,在此進行指定。
在運維過程中,我們經常會查看到一個 RPM 包的 Name、Version、Release。這個字段就是在 SPEC 文件中定義的。
例如,我們要查詢 Python RPM 包版本,如下圖 16 所示:
圖 16. 查看 python 版本
在上圖的輸出中:python 是 Name、2.7.5 是 Version、58.el7 是 Release、x86_64 是 BuildArch。這些信息都是在 SPEC 中定義的。
接下來,我們介紹 RPM SPEC 文件中使用的語法。
SPEC
DirectiveDefinition%description完整描述了 RPM 中打包的軟件,可以包含多行并分成段落。%prep打包準備階段執行一些命令%build包含構建階段執行的命令,構建完成后便開始后續安裝。%install包含安裝階段執行的命令。%check包含測試階段執行的命令。%files需要被打包/安裝的文件列表。%changelogRPM 包變更日志。
在介紹了 SEPC 的格式和語法后,接下來我們介紹如何書寫 SPEC 并構建 RPM 包。
書寫 SPEC 文件
在打包新軟件時,可以通過 rpmdev-newspec 工具創建一個新的 SPEC 文件,然后據此進行修改。
首先,我們通過三個源碼文件生成三個 SPEC,如下圖 17 所示:
圖 17. 生成 SPEC 文件
點擊查看大圖
.SPEC 已經生成,如下圖 18 所示:
圖 18.查看生成的 SPEC 文件
接下來我們為三個 SRPM 書寫 SPEC,描述如下:
Software NameExplanation of examplebello基于 bash 書寫的。不需要構建但只需要安裝文件。 如果是預編譯的二進制文件需要打包,這種方法也可以使用,因為二進制文件也只是一個文件。pello基于 Python 書寫的軟件。用 byte-compiled 的解釋編程語言編寫的軟件,用于演示字節編譯過程的安裝和安裝生成的預優化文件。cello基于 C 書寫的軟件。用 natively compiled 的編程語言編寫的軟件,演示使用工具的常見構建和安裝過程以及編譯本機代碼。
由于三個 SPEC 修改的思路類似,因此我們只細致介紹 bello 的 SPEC 修改步驟。
生成的 bello.spec 文件內容如清單 5 所示:
清單 5. 自動生成的 bello.spec
# cat bello.spec
Name: bello
Version:
Release: 1%{?dist}
Summary:
License:
URL:
Source0:
BuildRequires:
Requires:
%description
%prep
%setup -q
%build
%configure
make %{?_smp_mflags}
%install
rm -rf $RPM_BUILD_ROOT
%make_install
%files
%doc
%changelog
修改后的 bello.spec 內容如清單 6 所示:
清單 6. 修改后的 bello.spec
[root@rpmlab-d710 ~]# cat ~/rpmbuild/SPECS/bello.spec
Name: bello
Version: 0.1
Release: 1%{?dist}
Summary: Hello World example implemented in bash script
License: GPLv3+
URL: https://www.example.com/%{name}
Source0: https://www.example.com/%{name}/releases/%{name}-%{version}.tar.gz
Requires: bash
BuildArch: noarch
%description
The long-tail description for our Hello World Example implemented in
bash script of DavidWei.
%prep
%setup -q
%build
%install
mkdir -p %{buildroot}%{_bindir}
install -m 0755 %{name} %{buildroot}%{_bindir}/%{name}
%files
%license LICENSE
%{_bindir}/%{name}
%changelog
* Tue Jun 29 2019 DavidWei - 0.1-1
- First bello package
- Example second item in the changelog for version-release 0.1-1
在修改完 SEPC 后,我們就可以根據源代碼和 SPEC 文件構建軟件包。
構建二進制 RPM 包
實際上,我們在構建二進制 RPM 包時,有兩種構建方法:
- 從源碼構建 SRPM,然后再構建二進制 RPM
- 直接從源碼構建二進制 RPM。
然而,在軟件開發中,我們通常會采用第一種方法,因為它有以下優勢:
- 便于保留的 RPM 版本的確切來源(以 Name-Version-Release 格式標注)。這對于 debug 非常有用。
- 需要在不同的處理器硬件平臺上使用 SRPM 構建二進制 RPM。
由于篇幅有限,本文只展示從源碼構建 SRPM,再從 SRPM 構建二進制 RPM 的步驟。
構建 Source RPM 和二進制 RPM
下面我們演示如何通過源碼和剛修改的 SPEC 文件構建 Source RPM 并在構建時指定-bs 參數(如果使用-bb 參數,則直接生成二進制 RPM),以便生成 SRPM,如下圖 19 所示:
圖 19. 構建 SRPM
首先,我們基于 SRPM 生成二進制 RPM。執行過程如清單 7 所示:
清單 7. 由 SRPM 構建二進制 RPM
# rpmbuild --rebuild ~/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm
Installing /root/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm
warning: bogus date in %changelog: Tue Jun 29 2019 DavidWei - 0.1-1
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.hNMkOC
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd /root/rpmbuild/BUILD
+ rm -rf bello-0.1
+ /usr/bin/tar -xf -
+ /usr/bin/gzip -dc /root/rpmbuild/SOURCES/bello-0.1.tar.gz
+ STATUS=0
+ '[' 0 -ne 0 ']'
+ cd bello-0.1
+ /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w .
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.0isn4Y
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd bello-0.1
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.epoHml
+ umask 022
+ cd /root/rpmbuild/BUILD
+ '[' /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64 '!=' / ']'
+ rm -rf /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64
++ dirname /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64
+ mkdir -p /root/rpmbuild/BUILDROOT
+ mkdir /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64
+ cd bello-0.1
+ mkdir -p /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64/usr/bin
+ install -m 0755 bello /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64/usr/bin/bello
+ /usr/lib/rpm/find-debuginfo.sh --strict-build-id -m --run-dwz --dwz-low-mem-die-limit 10000000 --dwz-max-die-limit 110000000 /root/rpmbuild/BUILD/bello-0.1
/usr/lib/rpm/sepdebugcrcfix: Updated 0 CRC32s, 0 CRC32s did match.
+ '[' noarch = noarch ']'
+ case "${QA_CHECK_RPATHS:-}" in
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-compress
+ /usr/lib/rpm/redhat/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1
+ /usr/lib/rpm/redhat/brp-python-hardlink
+ /usr/lib/rpm/redhat/brp-JAVA-repack-jars
Processing files: bello-0.1-1.el7.noarch
Executing(%license): /bin/sh -e /var/tmp/rpm-tmp.hV1l1H
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd bello-0.1
+ LICENSEDIR=/root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64/usr/share/licenses/bello-0.1
+ export LICENSEDIR
+ /usr/bin/mkdir -p /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64/usr/share/licenses/bello-0.1
+ cp -pr LICENSE /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64/usr/share/licenses/bello-0.1
+ exit 0
Provides: bello = 0.1-1.el7
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires: /bin/bash
Checking for unpackaged file(s): /usr/lib/rpm/check-files /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64
Wrote: /root/rpmbuild/RPMS/noarch/bello-0.1-1.el7.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.PCJIAr
+ umask 022
+ cd /root/rpmbuild/BUILD
+ cd bello-0.1
+ /usr/bin/rm -rf /root/rpmbuild/BUILDROOT/bello-0.1-1.el7.x86_64
+ exit 0
Executing(--clean): /bin/sh -e /var/tmp/rpm-tmp.ift0pO
+ umask 022
+ cd /root/rpmbuild/BUILD
+ rm -rf bello-0.1
+ exit 0
二進制 RPM 構建成功后,可以在~/rpmbuild/RPMS/中找到生成的二進制 RPM:
bello-0.1-1.el7.noarch.rpm,如下圖 20 所示:
圖 20. 查看生成的二進制 RPM
通過 SRPM 構建成二進制 RPM 后,源碼會被自動刪除。如果想恢復源碼,需要安裝 srpm,如下圖 21 所示:
圖 21. 安裝 SRPM 并查看源代碼
現在我們檢查生成的二進制 RPM 的正確性并進行安裝。
檢查并安裝 RPM 包
Rpmlint 命令可以檢查二進制 RPM、SRPMs 和 SPEC 文件的正確性。
我們以 bello.spec 為例進行檢查。
# rpmlint bello.spec
bello.spec: E: specfile-error warning: bogus date in %changelog: Tue Jun 29 2019 DavidWei - 0.1-1
0 packages and 1 specfiles checked; 1 errors, 0 warnings.
從 bello.spec 的檢查結果中,發現一個 error。具體報錯描述我們需要檢查 srpm。
# rpmlint ~/rpmbuild/SRPMS/bello-0.1-1.el7.src.rpm
bello.src: W: invalid-url URL: https://www.example.com/bello HTTP Error 404: Not Found
bello.src: E: specfile-error warning: bogus date in %changelog: Tue Jun 29 2019 DavidWei - 0.1-1
1 packages and 0 specfiles checked; 1 errors, 1 warnings.
從檢查 SRPM 的結果可以看出,報錯的原因是 URL(
https://www.example.com/bello)無法訪問。我們修改 SEPC,將地址設置為可訪問地址,如下圖 22 所示:
圖 22. 修改 SPEC 設置 URL 為可訪問地址
修改成功后再重新編譯后檢查,重新驗證二進制 RPM 正確性,error 數量為 0,如下圖 23 所示:
圖 23. 驗證二進制 RPM 的正確性
最后,安裝編譯好的 rpm 包并運行程序進行驗證,如下圖 24 所示:
圖 24. 安裝二進制 RPM 包并執行程序
我們看到,上圖中執行 bello 程序成功,證明 RPM 安裝成功。
如何在異構環境重新編譯 RPM
在前文中我們已經提到,有的 RPM 包與運行環境有關,有的無關。如果一個 RPM 依賴于某一個版本的運行環境(linux 版本或處理器架構),我們如何讓這個 RPM 在其他的環境中運行?這涉及到異構環境下的 RPM 重新編譯。
Mock 是一個用于構建 RPM 包的工具(就像 Docker 啟動一個 build 的環境一樣,擺脫對編譯環境 linux 版本的限制)。它可以為不同的架構、Linux 版本構建 RPM 軟件包。在 RHEL 系統上使用 Mock,需要啟用"Extra Packages for Enterprise Linux"(EPEL)存儲庫。
針對 RPM 包,Mock 最常見的用例之一是創建原始的構建環境。通過指定不同的配置文件(/etc/mock 目錄下)即模擬使用不同的構建環境。
查看 mock 配置文件,如下圖 25 所示:
圖 25. 查看 mock 配置文件
我們以 epel-7-x86_64.cfg 為例,查看其中關于架構的描述,如清單 8 所示,可以看到是 x86_64 和紅帽 Linux 發行版 7 的信息:
清單 8. 查看 epel-7-x86_64.cfg 配置文件中架構信息
[root@master mock]# cat epel-7-x86_64.cfg |grep -i arch
config_opts['target_arch'] = 'x86_64'
config_opts['legal_host_arches'] = ('x86_64',)
mirrorlist=http://mirrorlist.centos.org/?release=7& arch=x86_64& repo=os
mirrorlist=http://mirrorlist.centos.org/?release=7& arch=x86_64& repo=updates
mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=epel-7& arch=x86_64
mirrorlist=http://mirrorlist.centos.org/?release=7& arch=x86_64& repo=extras
mirrorlist=http://mirrorlist.centos.org/?release=7& arch=x86_64& repo=sclo-rh
mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=testing-epel7& arch=x86_64
mirrorlist=http://mirrors.fedoraproject.org/mirrorlist?repo=epel-debug-7& arch=x86_64
使用 epel-7-x86_64 配置來構建 SRPM,如下圖 26 所示:
圖 26. 使用 epel-7-x86_64 構建二進制 RPM
使用 epel-6-x86_64 配置來構建 SRPM,如下圖 27 所示:
圖 27. 使用 epel-6-x86_64 構建二進制 RPM
查看構建好的二進制 rpm:
cello-1.0-1.el6.x86_64.rpm,如下圖 28 所示:
圖 28. 查看構建好的二進制 RPM
安裝
cello-1.0-1.el6.x86_64.rpm,如下圖所示:
圖 29. 安裝構建好的二進制 RPM
點擊查看大圖
查看構建好的二進制 RPM
cello-1.0-1.el7.x86_64.rpm,并進行安裝驗證,如下圖 30 所示:
圖 30. 安裝構建好的二進制 RPM
至此,在異構環境下重新編譯二進制 RPM 成功。
結束語
通過本文,相信您對通過源碼構建成 RPM 有了較為深刻的理解。隨著開源理念的不斷普及,越來越多的客戶將業務系統從 windows 遷移到 Linux 上,理解了 Linux 中的 RPM 打包方式,會對以后我們日常的工作有很大的幫助。
原文:
https://www.ibm.com/developerworks/cn/linux/l-lo-rpm-build-package/index.html