Open Source

Making RPMs, part 2: Preparing and compiling

In the second installment of his series on RPMs, Vincent continues to show you how to build the .spec file. You'll learn that when you create your RPM, you're basically creating an elaborate shell script to compile and install the package for you.


In this Daily Drill Down, we will continue to build the .spec file for the rpmproc program we began in Making RPMs, part 1: The .spec file header. We previously looked at how to begin creating an RPM, downloading the necessary source package, and beginning to write the .spec file. Now that the header of the RPM is almost complete, we can continue with the next steps in building this RPM.

The final step in finishing the .spec file header is the Description tag, which we would write like this:
%description
rpmproc is a simple Perl program to help manage and build RPM packages
for those who want an easy wrapper to build RPMs. rpmproc supports
announcing changelogs to a mailing list using either qmail or sendmail,
 signing RPMs, uploading to a user-defined "contribs" site, as well as
uploading to a local directory (ie., for systems that also make their
RPMs available via FTP).


To begin the description, use the %description tag. Everything following the %description tag until the next tag is reached is a part of the RPM description. Recall that earlier we defined the Summary field, which provided a very brief description of the program. The Description tag is there to allow you to describe the program in detail. Feel free to indent itemized lists, include blank lines, and so forth. The above description is a relatively simple explanation of what the rpmproc program does.

Now that you have completed the Description tag, let’s begin the actual build of the program.

Preparation and setup
From this point forward, until the end of your RPM .spec file, you must think of each section in one simple way. After defining a section with its Description tag, you must basically write a BASH script that executes what you want the RPM to do during that phase of building, installation, or removal of the RPM package.

For example, the first section we will be looking at is the %prep section. Everything between the %prep tag and the next tag is a single shell script that RPM will execute to prepare the RPM package build. You do not need to begin each script with the typical #!/bin/sh first line of BASH shell scripts, but you need to keep in mind the syntax and usability of shell scripts when designing your RPM. For simple RPM packages, like rpmproc, knowing the ins and outs of shell scripting isn’t that big an issue. But for more complex packages (like an RPM for Qmail, for example), you may want to have a basic understanding of what you can do in a shell script. It will make building complex RPM packages much easier.

The first thing RPM does when it builds a package is execute the %prep section. Insert the following into your rpmproc.spec file:
%prep -q

Pretty simple. The %prep section is typically used to prepare the script for building, which means unarchiving the source package. This must be the first entry after you enter the description of the RPM. You can simply use
%prep

if you like, instead. We’re adding the -q parameter to tell RPM to be quiet, or not to output the results of the tar command as it unarchives the file. The next thing we want to add to the prep script is the %setup macro, which we add like this:
%setup

immediately after the %prep identifier. The %setup macro does a number of things to prepare the source package, including unarchiving it. Typically, the %setup macro expands to accomplish a few things. First it cleans out the contents of any existing subdirectory for this package in the /usr/src/RPM/BUILD directory tree (for example, for this package, the first thing %setup does is remove /usr/src/RPM/BUILD/rpmproc-1.3 if it exists). Then it unarchives the source file by untarring it (regardless of whether it is a GZipped tar file or a bzipped tar file. Finally, it changes the current directory to the newly created subdirectory.

The %setup macro can be used to accomplish more than this, however. You can change the name of the build directory by passing it the -n command, like this:
%setup -n [name]

For example, suppose rpmproc-1.3.tar.bz2 did not create the directory ./rpmproc-1.3/ when it unarchived. Suppose all the files were stored in the ./rpmproc/ directory. Since RPM expects the subdirectory that the files will be stored in to match the %{name}-%{version} convention (or name - version), it would exit with an error if /usr/src/RPM/BUILD/rpmproc were created instead of /usr/src/RPM/BUILD/rpmproc-1.3. To get around this and tell RPM where the files actually are, we would use:
%setup -n rpmproc

Another possibility may exist. What if the source tar doesn’t store files in a top-level directory? What if you extract the files and they end up in the current directory? %setup can handle this, as well. If you pass the -c command to %setup, it will create a top-level directory for you. For example, if rpmproc-1.3.tar.bz2 stored the files in such a way that they would be untarred to the current directory, you could use:
%setup -c

and the resulting files from the source file would be untarred into the ./rpmproc-1.3/ subdirectory. You can also combine the -c and -n commands. The directory specified with the -n command will be created by the -c command and the contents of the source file will then be unarchived to it.

Building the source files
The next part of the .spec file is the %build section, which is the script that actually builds the source packages. Because rpmproc does not have anything to compile, we only need to specify the %build section, without including anything in it, like this:
%build

And that’s it. However, since most RPMs do require you to compile the source packages, let’s take a look at how we would accomplish this. A very simple and relatively generic %build script might look like this:
%build
if [ -d $RPM_BUILD_ROOT ]; then rm -rf $RPM_BUILD_ROOT; fi
mkdir -p $RPM_BUILD_ROOT
CFLAGS="$RPM_OPT_FLAGS" ./configure —prefix=%{prefix}

make


The first line basically checks to see if our Buildroot directory exists (remember, you defined it in the header section with a value of %{_tmppath}/%{name}-%{version}, which would translate to /var/tmp/rpmproc-1.3 for the rpmproc.spec file). If it exists, we delete the directory so that past compiles do not interfere with the current compile, whether they are for the same or older versions of the program. This effectively gives us a clean starting point.

The second line makes the Buildroot directory. What we do with the Buildroot directory is relatively simple. As the name implies, we use this as our root directory for building the package. The reason for this is pretty straightforward. Instead of messing up your current installation of a program (Or your system if you do something wrong!), we install files for this program to the Buildroot directory. For example, if a file belongs in the /usr/bin directory, we don’t want to place it in our /usr/bin directory. Instead, we want to install it to /var/tmp/rpmproc-1.3/usr/bin. RPM knows that the file stored in /var/tmp/rpmproc-1.3/usr/bin actually belongs in the /usr/bin directory, and it will place it there when the finished RPM is being installed. But for packaging purposes, the Buildroot directory makes life a little easier by segregating the virtual build root directory from the system’s actual root directory, thus preserving our current Linux environment and allowing us to create, install, delete, and otherwise manipulate the files that belong to our RPM package.

Finally, the third line runs the configure program as found in the build directory (the same directory specified in the %prep section, or /usr/src/RPM/BUILD/rpmproc-1.3/ in the case of the rpmproc program).

Not every program will have a configure program for you to run. The configure program generally creates the Makefile(s) you will execute to actually make the program binaries. The configure program sets up some options that the Makefile will need and allows you to tailor the compile to your environment.

Now, we know that not every program uses configure. You should also be aware that not every configure program runs the same way. Many configure programs supply options specific to the application, so these options may not be present for other programs, and vice versa. Some configure programs are extremely simplistic. The above is an example of a simplistic configure program. When in doubt, running the following command will usually give you a list of the options configure can handle:
./configure —help

For the above example, we are simply passing the prefix command to configure. Most configure programs have this option. This tells the Makefile where we want the resulting binaries to be installed. If you defined
Prefix:       /usr/bin

in your .spec file header, the %{prefix} macro expands to /usr/bin, in essence specifying
./configure ?prefix=%{prefix}
is the same as specifying:
./configure ?prefix=/usr/bin


The reason for using %{prefix} and not just /usr/bin when passing the prefix to configure is so we can create a relocatable package. In some instances, path information is hard-coded into a program, and if you want to relocate a package, you may need to rebuild the RPMs from the .spec file. Using %{prefix} in place of a hard-coded path means that someone can just change the value of Prefix in the header, recompile the package, and relocate the binaries to a directory of their choice.

Now, in the above example we also had a command previous to running configure. The full command was:
CFLAGS="$RPM_OPT_FLAGS" ./configure —prefix=%{prefix}

The CFLAGS=”$RPM_OPT_FLAGS” command prior to running configure sets the environment variable CFLAGS to the value of the macro $RPM_OPT_FLAGS. The CFLAGS variable is typically used to pass optimization flags to the compiler. Because Pentium computers use an enhanced set of instructions compared to 386 computers, we can optimize what we compile for a specific platform. Similarly, programs compiled for Alpha or Sparc computers use a different set of instructions for those processors as compared to general Intel computers. By giving the CFLAGS variable the value of $RPM_OPT_FLAGS, we are able to compile RPMs that target a specific platform. For example, when compiling for Pentium computers (i586), we can use flags that take advantage of Pentium-specific code enhancements. Those flags are stored in the value of $RPM_OPT_FLAGS.

This gives us quite a bit of flexibility. This means we can compile enhanced binaries for Pentium, Pentium II, Alpha, Sparc, or other hardware platforms without changing the .spec file.

The final command in this section is the make command. This does the actual binary compilation.

Conclusion
You now know how to create the entire header for the RPM, as well as how to prepare and compile very basic programs. You’ve also learned that each section of the RPM is, in essence, its own shell script in which we can use standard BASH commands to accomplish what we want to do. For anyone who has compiled their own programs under Linux, it’s obvious that the RPM .spec file mimics the commands you enter on the command line. Keeping that piece of information in mind is the trick to RPM creation. When creating your RPM, you’re basically creating an elaborate shell script to compile and install the package for you.

In part three of this series, we will take a look at the actual installation of files into our virtual build root directory and how to put everything together into an RPM package.

Vincent Danen, a native Canadian in Edmonton, Alberta, is an avid Linux "revolutionary" and a firm believer in the Open Source philosophy. He attempts to contribute to the Linux cause in as many ways as possible, from his Freezer Burn Web site to local advocacy in his hometown. Owner of a Linux consulting firm, Vincent is also the security updates manager for MandrakeSoft, creators of the Linux-Mandrake operating system. Vincent is a certified Linux Administrator by Tekmetrics.com.

The authors and editors have taken care in preparation of the content contained herein but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for any damages. Always have a verified backup before making any changes.

About Vincent Danen

Vincent Danen works on the Red Hat Security Response Team and lives in Canada. He has been writing about and developing on Linux for over 10 years and is a veteran Mac user.

Editor's Picks