Open Source

Making RPMs, part 4: Finishing the .spec file

In the fourth installment of his RPM series, Vincent will show you how to finish writing the .spec file and prepare for building the RPM itself.


Welcome to part four of “Making RPMs.” In this Daily Drill Down, we’ll finish 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 our RPM creation, from downloading the necessary source package to beginning to write the .spec file. In “Making RPMs, part 2: Preparing and compiling,” we looked at preparing and compiling the source code to create binaries for our distribution program. In “Making RPMs, part 3: Installing files,” we looked at different ways to install files within your RPM building environment. In this fourth Daily Drill Down on making your own RPMs, we’ll finish writing the .spec file and prepare ourselves for building the RPM itself.

The files section
In the third Daily Drill Down in this series, we examined the %files section of the .spec file. We looked at the attributes of the files included in our file list, as well as an easy way to install documentation to the /usr/doc directory tree. We put the following into our rpmproc.spec file:
%files
%defattr(-,root,root)
%doc README COPYING COPYRIGHT
/usr/bin/rpmproc
/etc/rpmproc.conf


The %files section is basically one long list of files to include in the RPM package. Any file in your BuildRoot environment not listed here won’t be included in the binary package RPM creates. For this reason, it’s a good idea to know what files exist and where.
Making RPMs, part 1: The .spec file headerMaking RPMs, part 2: Preparing and compilingMaking RPMs, part 3: Installing files
You can do this easily with small programs like rpmproc. It becomes a little more challenging when you have a package that places a lot of files in many different places. There are a few ways you can go about building your file list, and there are benefits to each method.

The easiest way to go about it is something as simple as
%files
%defattr(-,root,root)
%doc README COPYING COPYRIGHT
/usr/*
/etc/*


Unfortunately, this method is almost too simple. It will include all files in the /usr and /etc directory trees of our BuildRoot environment. It’s also considered extremely bad form, and RPMs that use this are usually considered poorly written. Although it’s completely valid, it’s not recommended, primarily because there’s no way to specify some important classifications to the files, as you’ll see a little later on.

Another alternative is to use the %files -f [file.list] command. When you pass -f to the %files script, it will take the contents of the file file.list as the listing of files to use. The first thing you’ll need to do is create the file listing, however. The following script, when included at the end of the %install section, will do exactly what you need:
%install
...
find . -type f | sed -e 's,^\.,\%attr(-\,root\,root) ,' \
-e '/\/etc\//s|^|%config|' \
  -e '/\/config\//s|^|%config|' \
  $RPM_BUILD_DIR/file.list.%{name}

find . -type l | sed -e 's,^\.,\%attr(-\,root\,root) ,' >> \
  $RPM_BUILD_DIR/file.list.%{name}


Basically, what we’re doing here is using the find program to give us some file types and then pipe the output to a file called
/usr/src/RPM/BUILD/file.list.rpmproc

We’re using the sed program to find files in any /etc or /config directories and flagging them as configuration files. Because of the flexibility of sed, we’re able to make a very efficient file list using these few lines in the %install script. Then, for your %files section, you’d simply use something like this:
%files -f ../file.list.%{name}
%defattr(-,root,root)


The %files section is now told to use ../file.list.%{name} as the input for the files that are to be included in the RPM. The reason you must specify the ../ before the filename is because the RPM is of course using
/usr/src/RPM/BUILD/%{name}-%{version}/

as the primary Build directory, and since we’re writing the file list to the /usr/src/RPM/BUILD directory, we have to tell RPM to move up one directory to access the file list.

One other option is to use a single line of code that accomplishes the same thing but allows us to implement things differently. Some distributions, like Linux Mandrake, do not like to include an arbitrary file list, but rather need to have the files actually listed in the %files section. The reason for this, at least in the case of Linux Mandrake, is a program that it uses called spec-helper. The spec-helper program strips binaries, compresses man pages, and so forth; in order for it to accomplish all of these things, the file list must be an actual list and not read from another file. So in this instance you’d use something like the following in your %install section:
find $RPM_BUILD_ROOT%{_prefix} -type f -print | sed "s@^$RPM_BUILD_ROOT@@g" \
| grep -v perllocal.pod > $RPM_BUILD_DIR/tmp-filelist


This single line of code accomplishes the same thing as the previous lines of code but with one difference: It does not flag any files as configuration files but lists them in a regular listing of files suitable for including in your .spec file.

In this instance, you wouldn’t include the list with the -f command. With this listing, you’d need to insert the /usr/src/RPM/BUILD/tmp-filelist file into your .spec file in the %files section. Then you’d make any necessary modifications to the file list once it was included in your .spec file. This method requires a two-step operation. The first build of your RPM file is a dummy build in order to generate the file list. After the dummy build, you must re-edit your .spec file and include the contents of tmp-filelist. At the same time, to keep the .spec file clean, remove the find command from the %install section. It’s necessary to insert that line of code only for the dummy build, and it should be removed after you’ve generated your file list, prior to building a production RPM.

Macros in the files section
I mentioned in the last Daily Drill Down of this series how versatile the %files section is. Now let’s take a look at some of the many macros you can use in the %files section. These macros apply only if you have the file listing inside the .spec file and do not include the file listing with the -f command. I highly recommend including the files directly in the .spec file as opposed to the inclusion method, simply because of the greater flexibility offered. Besides the %doc macro that was illustrated in the previous Daily Drill Down, there are a number of other macros you can use.

The %config macro is used to specify configuration files for this package. For example:
%config /etc/rpmproc.conf

specifies /etc/rpmproc.conf as a configuration file.

The %attr macro is similar to the %defattr macro except that it pertains specifically to the file listed. It’s used to specify attributes on files that differ from the defined default attributes (with %defattr) or if you don’t want to use %defattr but want to specify some attributes for a particular file. For example:
%attr(0755,root,mail) /usr/bin/mutt_dotlock

This would give the file /usr/bin/mutt_dotlock a file mode of 0755, owned by the user root and the group mail. The syntax for %attr is
%attr([mode],[user],[group]) file

As is the case with the %defattr macro, any attribute may be unspecified by replacing it with a - character. If an attribute is unspecified, it will use the default attribute (if specified) or the file’s current attribute as found on the file itself.

The %docdir macro is used to specify an entire directory as containing documentation. In this instance, every file in that directory (and in any subdirectories) will be flagged as documentation. This macro is an easy way to specify a number of files as documentation, as opposed to flagging each and every file with the %doc macro.

The %dir macro is used to add an empty directory to the package. If there are any files in this directory that you want included, you must manually specify them in the file list. This is useful if you need to create a directory that may not contain files during the installation phase but needs to exist for the program to write to and operate correctly (for logs or system information, for example).

Pre- and post-install scripts
In some cases, you may need to execute scripts before and after the installation of a particular package—and possibly before and after the removal of the same package. There are a number of reasons why this might be necessary. One of them is installing programs with new libraries. When a new library is installed on your system, you need to run ldconfig, and you also need to run it when you remove the library to keep the library information on your Linux system current. Another reason might be if you’re building RPMs for Linux Mandrake 7.1 or higher, which uses a new menuing system. After the installation of a package, you’ll want to regenerate the X menus to include the menu entry for the new package—likewise if you uninstall it.

For instances like these, you can include a few useful scripts in your RPM package. These scripts are called %pre, %post, %preun, and %postun, which are used for pre-install, post-install, pre-uninstall, and post-uninstall, respectively. There’s no particular order in which these scripts should be placed inside your .spec file, but for the sake of readability, I suggest including these scripts after the %install script.

A very simple set of scripts to update system library information follows:
%post
/sbin/ldconfig

%postun
/sbin/ldconfig


These two scripts simply call the ldconfig program (usually placed in the /sbin directory) after the installation of the RPM and after the removal of the RPM package.

The RPM ChangeLog
Just as many programs include a ChangeLog with the source code, RPM allows you to embed a ChangeLog for the RPM directly inside the .spec file. This helps identify what changes have been made to the RPM package for other users. It should list changes made to the .spec file, upgrades to new versions, and so forth.

The %changelog directive identifies that everything following is to be included as part of the ChangeLog. Because some RPMs have been around for a while and can contain very large ChangeLogs, it is recommended that the %changelog directive be the last one in your .spec file.

An example ChangeLog from the sawfish RPM I built looks like this:
%changelog
* Tue May 23 2000 Vincent Danen <vdanen@linux-mandrake.com> 0.27.2-2mdk-update BuildPreReq to include rep-gtk and rep-gtk-gnome
* Thu May 11 2000 Vincent Danen <vdanen@linux-mandrake.com> 0.27.2-1mdk-0.27.2
* Thu May 11 2000 Vincent Danen <vdanen@linux-mandrake.com> 0.27.1-1mdk-added BuildPreReq-change name from Sawmill to Sawfish


The format of entries in the %changelog section should be
* [dow mon dd yyyy] [packager [email address]] [RPM version]-list of changes

With Linux Mandrake RPMs, the RPM version must be included as the last item of the identifying date/packager line. Although not mandatory for other distributions, it gives anyone looking at the .spec file a good idea of when a particular change was implemented and for which RPM build it was done.

The list of changes can be as long as is necessary, but keep in mind that this should be a list of changes for the RPM itself and not for the package. It’s considered bad form to include the ChangeLog of the actual program in the RPM ChangeLog.

Multipackage RPM files
There may also be times when the RPM you wish to create needs to be split into multiple RPM packages. An excellent example of this is files that include development header files and source code. The average user installing these programs doesn’t need these files installed, so instead of creating one package containing everything, one package is created (i.e., openssl), and then a second package is created containing the development files (i.e., openssl-devel). This is called creating a primary RPM package and RPM subpackages.

Creating multipackage RPM files is not difficult, but it does require a little more work. Directly after the %description section, you’ll want to specify the multiple packages. For this example, we’ll look at the sawfish package, which, when built, creates two RPM files: sawfish and sawfish-gnome. After the %description section, we’d include this code:
%package gnome
Summary:      GNOME support for sawfish
Group:        Graphical desktop/Sawfish
Requires:     %{name}

%description gnome
Optional GNOME support for sawfish. Includes a wm-entries spec, and a control center applet.


The %package macro takes the name of the subpackage as the only argument. It’s basically designed to create the header for the subpackage. Any normal header keyword that’s unspecified takes its value from the primary header, so you need to specify only those keywords that should be different. In the above example, we want to change the Summary, Group, and Requires keywords. Everything else remains the same.

You can also pass -n to the %package directive to get a different package name. By default, when you use just the subpackage name, the resulting RPM is named with the package-subpackage convention. For the previous example, the resulting name of our subpackage is sawfish-gnome. If, however, you wanted the subpackage to be called gnome-sawfish, you would use
%package -n gnome-sawfish

Using -n tells RPM to use the name specified for the package as the absolute name and not a name relative to the primary RPM name, as is used by default.

The %description macro is again used, but this time it’s passed the name of the subpackage as an argument. This means that the description that follows is to be used only with that subpackage.

The only other section that you really need to worry about in multipackage RPMs is the %files section. The regular %files section is used for the primary package (in this case, sawfish), while a second %files section must be defined with the subpackage name as the argument. For example, the %files section for the sawfish-gnome package would look like this:
%files gnome
%defattr(-,root,root)
%{prefix}/bin/sawfish-capplet
%{prefix}/share/control-center/Sawfish
%{prefix}/share/gnome/apps/Settings/Sawfish
%{prefix}/share/gnome/wm-properties/Sawfish.desktop


This code should follow the primary %files section. It’s identical to the %files section in all respects except that the files listed in the subpackage should not be listed in the primary package. The way to look at multipackage RPMs is that the primary package will be the one most people will install, while subpackages would be installed as add-ons or development packages.

You can also pass the subpackage name to all the %pre, %post, %preun, and %postun scripts if you need to have different actions upon installation or uninstallation, just as you pass the subpackage name to the %description and %files directives.

Conclusion
Congratulations! You now have the basics at your fingertips to create your own RPM .spec file! In the past four Daily Drill Downs, we have examined the .spec file from top to bottom and have introduced many macros and scripts to make building RPMs as simple and intuitive as possible. Now that your .spec file is complete, the only thing left is the actual RPM build, which is what we’ll do in the fifth and last part of this series, “Making RPMs, part 5: Building the RPM.”

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