RPM打包指南(2)——准备打包的软件

本章主要讲解如何编写源码和创建软件,对于RPM文件的打包者来说,这些是必备的背景知识。

什么是源码?

源码是人类可读的计算机指令,用于描述如何执行一次计算。可以使用一种编程语言来编写源码。

本教程会提供三个版本的Hello World程序,每个版本都使用一种不同的编程语言来编写。使用这三种不同的语言编写的程序,它们的打包方法也是不同的,涵盖RPM打包者可能会遇到的三种主要的使用场景。

注意:
现在已经有数千种编程语言。本文只会使用三种编程语言,它们足以描述一些基本概念。

  • 使用bash编写的Hello World程序,文件名为bello
  1. #!/bin/bash
  2. printf "Hello World\n"
  • 使用Python编写的Hello World程序,文件名为pello.py
  1. #!/usr/bin/env python
  2. print("Hello World")
  • 使用C语言编写的Hello World程序,文件名为cello.c
  1. #include <stdio.h>
  2. int main(void) {
  3. printf("Hello World\n");
  4. return 0;
  5. }

上述的三个程序都可以将Hello World信息输出至命令行。

注意:
对于软件打包者来说,了解如何编写程序并不是必需的,但是会很有帮助。

程序是如何生成的

有很多种方法可以将人类可读的源码转换成机器代码 —— 实际上,计算机会遵循这些指令来执行程序。然而,可以将所有方法简化为以下三种:

  1. 程序是本地编译的。
  2. 程序是通过原生解释器解释执行的。
  3. 程序是在编译成字节码之后解释执行的。

本地编译代码

本地编译的软件是使用一种需要编译成机器码的编程语言来编写的,编译之后会得到一个二进制可执行文件。这种软件可以独立运行。

通过这种方式构建的RPM软件包是特定于体系结构的。这就意味着,如果你在一台使用64位(x86_64)AMD或Intel处理器的计算机上编译这种软件,那么得到的可执行文件是不能在32位(x86)AMD或Intel处理器上执行的。编译得到的软件包的文件名会包含体系结构的相关信息。

解释代码

某些编程语言,如bashPython,不会编译成机器代码。相反,它们的程序源码不用经过事先转换就可以逐步执行,通过某种语言解释器或语言虚拟机解释执行。

完全使用解释型编程语言编写的软件不会特定于体系结构。因此,编译得到的RPM软件包的文件名包含noarch字符串。

解释型语言有两种:字节码编译型原生解释型。这两种类型的语言的不同之处在于程序构建过程和软件打包过程。

  • 原生解释型的程序
    使用原生解释型语言编写的程序根本不需要编译,它们直接由解释器执行。

  • 字节码编译型的程序
    使用字节码编译型语言编写的程序需要编译成字节码,然后再由语言虚拟机执行。

注意:
某些编程语言会给出选项:它们可以是原生解释型或字节码编译型。

从源码构建软件

本节会解释如何从源码构建软件。

  • 对于使用编译型语言编写的软件来说,源码需要经过一个构建的过程才能生成机器代码。这个过程通常称为编译翻译,因不同语言而异。可以运行或执行构建得到的软件,计算机便会执行由程序员指定的任务。

  • 对于使用原生解释型语言编写的软件来说,不需要构建源码,可以直接执行。

  • 对于使用字节码编译型语言编写的软件来说,需要将源码编译成字节码,然后再由语言虚拟机执行。

本地编译代码

在这个示例中,你将会把使用C语言编写的cello.c程序构建成一个可执行程序。cello.c的源码,如下所示:

  1. #include <stdio.h>
  2. int main(void) {
  3. printf("Hello World\n");
  4. return 0;
  5. }

1. 手动构建

调用GNU编译器集合(GCC)中的C语言编译器,将源码编译成二进制文件:

  1. gcc -o cello cello.c

执行编译输出的二进制文件cello

  1. $ ./cello
  2. Hello World

就这些了!你已经将源码本地编译成一个二进制可执行程序,并且成功地运行这个可执行程序。

2. 自动构建

除了手动构建源码之外,你还可以将构建过程自动化。这是大型软件经常使用的一种方法。创建一个名为Makefile的文件,然后运行GNU的<a href="http://www.gnu.org/software/make/" title="make“>make工具,这样便能实现自动化构建了。

为了设置自动化构建,你需要在cello.c文件所在的相同目录之中,创建一个名为Makefile的文件。Makefile文件的内容,如下所示:

  1. cello:
  2. gcc -o cello cello.c
  3. clean:
  4. rm cello

现在构建软件,简单地运行make工具:

  1. $ make
  2. make: 'cello' is up to date.

这是因为已经存在编译好的二进制文件了,请运行make clean命令,然后再次运行make命令:

  1. $ make clean
  2. rm cello
  3. $ make
  4. gcc -o cello cello.c

在另一次构建完成之后,再次尝试构建,此时不会起任何作用:

  1. $ make
  2. make: 'cello' is up to date.

最后,执行程序:

  1. $ ./cello
  2. Hello World

现在,你不仅可以手动编译一个程序,而且还可以使用构建工具实现自动化编译。

解释代码

接下来的两个示例会演示如何运行一个字节码编译型的程序(使用Python编写),以及如何运行一个原生解释型的程序(使用bash编写)。

注意:
在下面的两个示例中,文件顶部的#!行被称为shebang,它不是编程语言源码的一部分。
shebang会使得系统将一个文本文件看作是一个可执行文件:系统程序加载器会解析包含shebang的行,获取二进制可执行文件的路径,然后会将这个可执行文件作为编程语言的解释器。

1. 字节码编译型的代码

在这个示例中,你将会把使用Python编写的pello.py程序编译成字节码,然后通过Python的语言虚拟机解释执行。Python源码还可以原生解释执行,但是编译成字节码的执行速度会更快。因此,RPM打包者更倾向于打包编译成字节码的软件,然后分发给终端用户。pello.py的源码,如下所示:

  1. #!/usr/bin/env python
  2. print("Hello World")

将程序编译成字节码的过程,对于不同的编程语言来说是不同的。编译过程取决于编程语言、语言的虚拟机,以及这种编程语言会使用的工具和流程。

注意:
Python经常编译字节码,但是并不以此处描述的方式执行。以下过程的目标并不是为了符合社区标准,而是为了简单。如果想要了解Python的技术指南,那么请参考软件打包和分发页面。

pello.py编译成字节码:

  1. $ python -m compileall pello.py
  2. $ file pello.pyc
  3. pello.pyc: python 2.7 byte-compiled

执行pello.pyc中的字节码:

  1. $ python pello.pyc
  2. Hello World

2. 原生解释型的代码

在这个示例中,你将会原生解释执行bello程序,它是用bash内建的语言编写的。bello的源码,如下所示:

  1. #!/bin/bash
  2. printf "Hello World\n"

使用shell脚本语言编写的程序,如bash,都是原生解释执行的。因此,你只需要将存放源码的文件设置为可执行的,然后运行即可:

  1. $ chmod +x bello
  2. $ ./bello
  3. Hello World

修补软件

补丁是一种源码,用于更新其他源码。补丁采用diff文件格式,因为它能够表示两个不同版本的文本的不同之处。使用diff工具可以创建diff文件,然后使用<a href="http://savannah.gnu.org/projects/patch/" title="patch“>patch工具将其应用至源码。

注意:
软件开发者经常会使用版本控制系统(如git)来管理他们的代码库。这些工具也具备创建diff文件或修补软件的功能。

在以下示例中,我们会使用diff工具从原始的源码创建一个补丁文件,然后使用patch工具应用这个补丁文件。当通过SPEC文件创建RPM文件时,将会对源码打补丁,稍后的章节将会描述如何打补丁。

打补丁是如何与RPM打包相关联的呢?在打包时,我们不会简单地编辑/修改原始的源码,而是保持不变,将补丁应用至这些源码。

若要为cello.c源码创建一个补丁:

Step-1. 保留原始的源码:

  1. $ cp cello.c cello.c.orig

这是保存原始的源码文件的常用方法。

Step-2. 修改cello.c源码:

  1. #include <stdio.h>
  2. int main(void) {
  3. printf("Hello World from my very first patch!\n");
  4. return 0;
  5. }

Step-3. 使用diff工具生成补丁文件:

注意:
我们使用了diff工具的几个常用参数。若要了解这些参数有关的详细信息,请参考diff的使用手册。

  1. $ diff -Naur cello.c.orig cello.c
  2. --- cello.c.orig 2016-05-26 17:21:30.478523360 -0500
  3. +++ cello.c 2016-05-27 14:53:20.668588245 -0500
  4. @@ -1,6 +1,6 @@
  5. #include<stdio.h>
  6. int main(void){
  7. - printf("Hello World!\n");
  8. + printf("Hello World from my very first patch!\n");
  9. return 1;
  10. }
  11. \ No newline at end of file

-符号开头的行,表示从原始的源码中删除的内容;以+符号开头的行,表示替换的内容。

Step-4. 将补丁保存至文件:

  1. $ diff -Naur cello.c.orig cello.c > cello-output-first-patch.patch

Step-5. 恢复原始的cello.c源码:

  1. $ cp cello.c.orig cello.c

我们保留了原始的cello.c源码文件,因为当构建RPM文件时,会使用原始的文件,而不是修改后的文件。想要了解更多的信息,请参考SPEC文件的使用手册。

若要使用cello-output-first-patch.patch文件给cello.c源码打补丁,则需要将补丁文件重定向至patch命令:

  1. $ patch < cello-output-first-patch.patch
  2. patching file cello.c

现在,cello.c文件的内容可以反映补丁的变更:

  1. $ cat cello.c
  2. #include<stdio.h>
  3. int main(void){
  4. printf("Hello World from my very first patch!\n");
  5. return 1;
  6. }

若要构建和运行打过补丁的cello.c程序,则需要:

  1. $ make clean
  2. rm cello
  3. $ make
  4. gcc -o cello cello.c
  5. $ ./cello
  6. Hello World from my very first patch!

你已经学会如何创建补丁文件、如何给程序打补丁,以及如何构建和运行打过补丁的程序。

安装任意工件

Linux和其他类Unix系统有一个很大的优势,那就是文件系统层次标准(FHS)。它会指定哪些文件应该位于哪些目录之中。RPM软件包安装的文件,应该按照FHS的要求,存放在合适的目录之中。例如,可执行文件应当存放在系统的PATH环境变量所指定的目录之中。

在本指南的上下文中,任意工件是指从RPM软件包安装到系统中的任何文件。对于RPM文件和系统来说,工件可能是一个脚本文件、一个从源码包编译得到的二进制文件、一个预编译的二进制文件或任何其他的文件。

有两种主流的方法可以将任意工件放置在系统之中,我们会探索这两种方法:使用install命令,以及使用make install命令。

使用install命令

有时候,使用自动化构建工具(如GNU make)并不是最佳的 —— 例如,如果待打包的程序非常简单,不需要额外的开销。在这些情况下,打包者经常会使用install命令(由系统的coreutils工具包提供),这个命令会将工件放置在文件系统的指定目录之中,并且会设置指定的权限集合。

以下示例将使用我们以前创建的bello文件作为我们的安装方法的任意工件。注意,你将需要sudo权限,或者能够以root权限运行这个命令(不包括命令的sudo部分)。

在这个示例中,install命令会将bello文件存放在/usr/bin目录之中,并且设置可执行脚本的常用权限:

  1. $ sudo install -m 0755 bello /usr/bin/bello

现在,bello程序已经存放在PATH环境变量列出的某个目录之中。因此,你可以在任意目录中执行bello程序,而不用指定它的完整路径:

  1. $ cd ~
  2. $ bello
  3. Hello World

使用make install命令

你还可以使用make install命令将已经构建的软件安装至系统之中,这是一种流行的自动化安装方法。你需要在Makefile文件中指定如何将任意工件安装至系统之中。

注意:
通常,Makefile文件是由开发者编写的,而不是由打包者编写的。

Makefile文件添加install节。Makefile文件的内容,如下所示:

  1. cello:
  2. gcc -o cello cello.c
  3. clean:
  4. rm cello
  5. install:
  6. mkdir -p $(DESTDIR)/usr/bin
  7. install -m 0755 cello $(DESTDIR)/usr/bin/cello

$(DESTDIR)变量是GNU make工具内建的,通常用于指定不同于根目录的安装目录。

现在,你不仅可以使用Makefile文件来构建软件,还可以通过这个文件将软件安装至目标系统。

为了构建和安装cello.c程序,你需要:

  1. $ make
  2. gcc -o cello cello.c
  3. $ sudo make install
  4. install -m 0755 cello /usr/bin/cello

现在,cello程序已经存放在PATH环境变量列出的某个目录之中。因此,你可以在任意目录中执行cello程序,而不用指定它的完整路径:

  1. $ cd ~
  2. $ cello
  3. Hello World

你已经将一个构建完成的工件安装至系统的指定位置之中了。

准备待打包的源码

注意:
本节创建的代码,可以在此处下载。

开发者通常会以源码压缩包的形式分发软件,然后会用这个压缩包来创建RPM软件包。在本节中,你将会创建这类的压缩包。

注意:
通常,RPM打包者不负责创建源码压缩包,这是开发者负责的工作。打包者会使用现成的源码压缩包。

软件应该使用某种软件许可证进行分发。例如,我们将会使用GPLv3许可证。每个示例程序都会有一个LICENSE文件,其中包含许可证文本。当打包时,RPM打包者需要处理许可证文件。

若要创建LICENSE文件,则可以参考以下示例:

  1. $ cat /tmp/LICENSE
  2. This program is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation, either version 3 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <http://www.gnu.org/licenses/>.

将源码转换成压缩包

在以下示例中,我们会将三个Hello World程序分别放入三个用gzip压缩的tar文件之中。软件通常以这种方式发布,稍后将被打包分发。

bello

bello工程使用bash的脚本语言实现Hello World。这种实现只包含’bello’的shell脚本。因此,除了LICENSE文件之外,得到的tar.gz压缩包只包含上述的脚本文件。我们假设程序的版本号是0.1

为软件分发准备bello工程:

Step-1. 将文件放入一个单独的目录:

  1. $ mkdir /tmp/bello-0.1
  2. $ mv ~/bello /tmp/bello-0.1/
  3. $ cp /tmp/LICENSE /tmp/bello-0.1/

Step-2. 创建分发的压缩包,然后将这个压缩包移动至~/rpmbuild/SOURCES/目录:

  1. $ cd /tmp/
  2. $ tar -cvzf bello-0.1.tar.gz bello-0.1
  3. bello-0.1/
  4. bello-0.1/LICENSE
  5. bello-0.1/bello
  6. $ mv /tmp/bello-0.1.tar.gz ~/rpmbuild/SOURCES/

pello

pello工程使用Python语言实现Hello World。这种实现只包含pello.py程序。因此,除了LICENSE文件之外,得到的tar.gz压缩包只包含上述的源码文件。我们假设程序的版本号是0.1.1

为软件分发准备pello工程:

Step-1. 将文件放入一个单独的目录:

  1. $ mkdir /tmp/pello-0.1.1
  2. $ mv ~/pello.py /tmp/pello-0.1.1/
  3. $ cp /tmp/LICENSE /tmp/pello-0.1.1/

Step-2. 创建分发的压缩包,然后将这个压缩包移动至~/rpmbuild/SOURCES/目录:

  1. $ cd /tmp/
  2. $ tar -cvzf pello-0.1.1.tar.gz pello-0.1.1
  3. pello-0.1.1/
  4. pello-0.1.1/LICENSE
  5. pello-0.1.1/pello.py
  6. $ mv /tmp/pello-0.1.1.tar.gz ~/rpmbuild/SOURCES/

cello

cello工程使用C语言实现Hello World。这种实现只包含cello.cMakefile文件。因此,除了LICENSE文件之外,得到的tar.gz压缩包只包含上述两个文件。我们假设程序的版本号是1.0

注意,patch文件并不会在压缩包中随着程序一起发布。当构建RPM文件时,RPM打包者将会对源码打补丁。补丁文件会和.tar.gz压缩包一起存放至~/rpmbuild/SOURCES/目录。

为软件分发准备cello工程:

Step-1. 将文件放入一个单独的目录:

  1. $ mkdir /tmp/cello-1.0
  2. $ mv ~/cello.c /tmp/cello-1.0/
  3. $ mv ~/Makefile /tmp/cello-1.0/
  4. $ cp /tmp/LICENSE /tmp/cello-1.0/

Step-2. 创建分发的压缩包,然后将这个压缩包移动至~/rpmbuild/SOURCES/目录:

  1. $ cd /tmp/
  2. $ tar -cvzf cello-1.0.tar.gz cello-1.0
  3. cello-1.0/
  4. cello-1.0/Makefile
  5. cello-1.0/cello.c
  6. cello-1.0/LICENSE
  7. $ mv /tmp/cello-1.0.tar.gz ~/rpmbuild/SOURCES/

Step-3. 添加补丁文件:

  1. $ mv ~/cello-output-first-patch.patch ~/rpmbuild/SOURCES/

现在,源码已经准备就绪,可以打包RPM文件了。