Open Source

Making RPMs, part 3: Installing files

Vincent covers installing the binaries, man pages, configuration files, and all the other components that belong in your RPM package in this third Daily Drill Down on creating your own RPMs.

Welcome to part three of “Making RPMs.” In this Daily Drill Down, we’ll come one step closer to finishing the .spec file for the rpmproc program we began in “Making RPMs, part 1: The .spec file header.” We have 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 this third Daily Drill Down on making your own RPMs, we’ll look at installing files for RPM to package.
Making RPMs, part 1: The .spec file headerMaking RPMs, part 2: Preparing and compiling
Installing the files
The section of the .spec file following the %build section is the %install section. This script installs the binaries, man pages, configuration files, and all other components that belong in your RPM package to their respective locations in our virtual build root directory. Remember defining the BuildRoot keyword back in the first part of this series? This is the virtual build root directory into which we’ll be installing our files, so we must observe a few very important points.

The $RPM_BUILD_ROOT macro is one you don’t want to forget. This expands into the directory you defined in the BuildRoot keyword statement back when we created the header for the RPM. Usually, you would define it as %{_tmppath}/%{name}-%{version}, which in the case of the rpmproc.spec file would expand to /var/tmp/rpmproc-1.3/. This is considered our top-level root directory by RPM.

The installation of rpmproc is pretty simple and illustrates perhaps one of the easier %install scripts you may encounter. Enter the following into your rpmproc.spec file:
%install
mkdir -p $RPM_BUILD_ROOT/usr/bin
install -m755 $RPM_BUILD_DIR/%{name}-%{version}/rpmproc $RPM_BUILD_ROOT/usr/bin
install -m644 $RPM_BUILD_DIR/%{name}-%{version}/rpmproc.conf $RPM_BUILD_ROOT/etc


The first line identifies that everything in this section belongs to the %install script. The second line creates the /usr/bin directory in our BuildRoot environment. Notice the use of the $RPM_BUILD_ROOT macro? It’s necessary to include that macro—otherwise we’ll end up installing into the system’s /usr/bin directory, which is the last thing we want to do. We also specify -p with the mkdir command, because we want it to create any parent directories if they don’t already exist.

The third line does the actual install. It takes advantage of the install program, specifying the file mode of 0755, or owner read/write/execute and group/world read/execute. This is typically the standard mode for most user-run executables on any Linux system. The $RPM_BUILD_DIR macro is also used here because it’s the directory that stores our top-level directory after unarchiving the source tar file, which expands to /usr/src/RPM/BUILD. Finally, we install the program into the /usr/bin directory of our BuildRoot environment.

While this technique appears relatively simple, not all installs are quite this easy. We’ll examine some other methods that you might need to know to accommodate other programs for which you may be trying to make an RPM package.

Many programs that use make to compile programs also use it to provide the installation. If you’re familiar with compiling your own programs under Linux, you should be familiar with the typical
./configure; make; make install

sequence of commands to configure, compile, and install the program onto your system. We can often use the make install command to do the same thing, with a slight modification. The Makefile contains the end location of each file that should be installed. These would be directories such as /usr/bin and /etc. So how do we adapt it to our BuildRoot environment? We must use the prefix command during the install. This is usually accomplished by placing
make prefix=$RPM_BUILD_ROOT install

in your .spec file. You should be careful not to assume that all Makefiles are written the same way, however. And you must also remember that Linux is case-sensitive, and some Makefiles may use prefix= or PREFIX=. I’ve also come across Makefiles that use DESTDIR= as the relocation variable. The best way to find out which command the Makefile uses is to take a direct look at the Makefile itself. It’s usually fairly obvious which variable name it uses.

The other possibility is that the Makefile has no convention for using an alternate destination root directory. This makes life a little more difficult, but depending on the complexity of the program (how many different files it installs, whether it creates symbolic links, etc.), we can still accomplish the install relatively simply.

One option is to use the diff and patch programs to modify the Makefile. I wouldn’t recommend this method to anyone who isn’t familiar with shell or C programming, however. If you’re familiar with both (or simply want to try it), then this is an effective way of solving a small problem.

If you want to use the patch/diff method of fixing the Makefile, you’ll need both utilities on your system. Unarchive the source tar file that contains the Makefile into a temporary location, copy the original Makefile to a file called Makefile.org, and then edit the Makefile with your favorite text editor. Make the changes you need; typically you’ll be looking for calls to the install program or copy (cp) commands. You can simply put the $(PREFIX) macro before the destination directory for these install or copy commands. For example, in the Makefile you might change
install -m755 source/thisfile /usr/bin/thisfile

to something like
install -m755 source/thisfile $(PREFIX)/usr/bin/thisfile

You then need to run the diff program on the two files to extract the differences between the files to make your patch file:
diff -run Makefile.org Makefile >program.patch

Redirect the output to an appropriate name. For example, if you were creating a patch for the program called myprogram, you might redirect the output to a file called myprogram-make.patch. Now use your favorite text editor to edit the newly created diff file. When you edit the file, you’ll see something like this:
---    Makefile.org  Wed Jun 14 15:34:35 2000
       +++ Makefile  Wed Jun 14 15:34:31 2000
       @@ -1 +1 @@
-      install -m755 source/thisfile /usr/bin/thisfile
       +install -m755 source/thisfile $(PREFIX)/usr/bin/thisfile


You now need to edit the first two lines of the file. Right now, they point to a file called Makefile in the current directory, and this will confuse RPM. Let’s assume that the Makefile file, when unarchived, is in the myprogram-1.0/ directory, meaning that when you unarchive the source tar file, it creates a top-level directory called myprogram-1.0/ and that the Makefile exists in that directory. In order for RPM to properly patch the Makefile file, you need to tell it exactly where that file exists. So we would change
--- Makefile.org     Wed Jun 14 15:34:35 2000
 +++ Makefile Wed Jun 14 15:34:31 2000


to
--- myprogram-1.0/Makefile.org    Wed Jun 14 15:34:35 2000
 +++ myprogram-1.0/Makefile Wed Jun 14 15:34:31 2000


It’s important not to put a slash before the directory name. When RPM is executing the patch, it will be in the /usr/src/RPM/BUILD directory, and the unarchived source file would be /usr/src/RPM/BUILD/myprogram-1.0/Makefile, which is the file we want to patch.

The final step is to tell RPM that the patch exists and to use it. Place the patch file into the /usr/src/RPM/SOURCES directory and edit the header of your .spec file to include
Patch0:myprogram-make.patch

You also need to edit your %prep script to make it look something like this:
%prep
%setup
%patch0 -p1


The important part here is the third line. This line tells RPM to execute the patch designated as Patch0, which we defined in the header. If you had multiple patch files, you would simply use %patch[x] -p1 and so on to accommodate the number of Patch[x] statements in your header, where [x] is the patch number.

The other method is to manually install everything. This might be easier for beginners and those unfamiliar with C or shell programming. Instead of modifying a Makefile, making a diff patch file, and including the patch in the program, you can manually use theinstall program to install the different files with appropriate permissions on each.

For this method to work successfully, however, it would be a good idea to first manually compile and install the program you’re building the RPM for, letting the Makefile install all the components of the program. This way you can see where it installs everything and what permissions it gives all the files associated with the program.

Let’s assume that myprogram installs three files: /usr/bin/myprogram, /usr/bin/otherprogram, and /etc/myprogram.conf. The permissions for the two executable files in /usr/bin are 755, and the configuration file in /etc is 644. To emulate this in the %install script of the .spec file, we’d use the following:
%install
mkdir -p $RPM_BUILD_ROOT/{/etc,/usr/bin}
install -m755 $RPM_BUILD_DIR/%{name}-%{version}/myprogram \
$RPM_BUILD_ROOT/usr/bin
install -m755 $RPM_BUILD_DIR/%{name}-%{version}/otherprogram \
$RPM_BUILD_ROOT/usr/bin
install -m644 $RPM_BUILD_DIR/%{name}-%{version}/myprogram.conf \
$RPM_BUILD_ROOT/etc


Examining this code step-by-step, we see that the first line begins the %install script. The second line creates two directories: /var/tmp/myprogram-1.0/etc and /var/tmp/myprogram-1.0/usr/bin. The third line installs /usr/src/RPM/BUILD/myprogram-1.0/myprogram to the /var/tmp/myprogram-1.0/usr/bin directory with the permissions set to 755. The fifth line does the same thing with the otherprogram file. Finally, the seventh line installs the myprogram.conf file into the /etc directory of our BuildRoot environment with the permissions set to 644.

As you can see, both methods of fixing poorly written Makefileinstall programs can be tedious. The first method, using diff and patch, is a little more difficult, but it can be the better method for a package that installs a large number of files. The second method of calling install manually is easier for a small number of files, but it can be difficult to keep track of for programs with a large number of files.

There are probably another million or so scenarios for the %install script. You may need to set symbolic links for files, or you may have to create users and groups and provide different user/group ownership to certain files. You may even need to use a Makefile to install the main program and manually install supplementary files from other source packages. Keep in mind that the %install script is just another BASH shell script, so you can use any common programs to do what you need. For example, you can easily call the useradd program if you need to create a new user on the system for the program. Also make sure that this is all done without requiring intervention. The last thing you want to do is make an RPM that requires human intervention during the install. Someone installing the RPM in the background, or by an automated method, probably won’t be around to type in the input that a particular program you choose to call may require. In fact, to look at a worst-case scenario, suppose that your RPM made it onto the distribution CD for a particular flavor of Linux. Wouldn’t it be terrible to know that your RPM caused the installation process to hang because it required human input that was unavailable due to the automated process of installing a large number of RPMs to create a working Linux system?

If you keep these few things in mind, you’ll have no problem creating quality RPM packages. Remember, if the end users wanted to fiddle with things themselves, they would be compiling and installing the program from the source package. The whole idea behind RPM packages, besides keeping a clean and coherent system, is to make installing programs easy for Linux users.

Good housekeeping
The next part of any .spec file should be the %clean script. This script provides a little housekeeping for the build system. Once you’ve created the RPM, there’s really no point to keeping files in the BuildRoot environment, or in the primary build directory, either. This is where the %clean script comes in. Insert the following into your rpmproc.spec file:
%clean
if [ -d $RPM_BUILD_ROOT ]; then rm -rf $RPM_BUILD_ROOT; fi
rm -rf $RPM_BUILD_DIR/%{name}-%{version}


Basically this script does two simple things. First it checks for the existence of the BuildRoot directory; if BuildRoot exists, the script removes it completely. Then it removes the build directory, in this case the /usr/src/RPM/BUILD/rpmproc-1.3/ directory. The second command must be modified if the source tar file doesn’t follow the typical name-version top-level directory convention. For example, if the source tar file unarchived into /usr/src/RPM/BUILD/rpmproc, then using the above command would generate an error and your build directory would not be removed. In this case, you’d use
rm -rf $RPM_BUILD_DIR/%{name}

to remove the /usr/src/RPM/BUILD/rpmproc directory.

Adding the files to your RPM
The next section of the .spec file is the %files section. This is a very important section as it tells RPM which files must be included in the binary packages. Insert the following into your rpmproc.spec file:
%files
%defattr(-,root,root)
%doc README COPYING COPYRIGHT
/usr/bin/rpmproc
/etc/rpmproc.conf


The first line identifies the lines that follow as the file listing. The second line provides the default attributes for the files in the list. Here we’re defining that each file should be owned by user root and group root, retaining whatever permissions are assigned to them. The syntax for %defattr is
%defattr([mode],[user],[group])

To exclude any of the attributes, use a - character, just as we did in the example, which excludes setting a default file mode attribute.

The next line identifies which files are part of the documentation. This has a dual purpose. The first is to identify the documentation files. The second is to include those files in the RPM archive in the /usr/doc/%{name}-%{version} directory. What the %doc directive accomplishes is quite ingenious. It pulls the files out of the build directory (in this case, the /usr/src/RPM/BUILD/rpmproc-1.3/ directory), automatically copies the files to the /usr/doc/rpmproc-1.3/ directory of our BuildRoot environment, and then adds them to the RPM package. If you wish to add files from a subdirectory, simply prefix the filename with the directory name, like this:
%doc somedir/README somedir/COPYING otherdir/otherfile COPYRIGHT

This will copy /usr/src/RPM/BUILD/program-version/somedir/README and COPYING (in the same directory), as well as /usr/src/RPM/BUILD/program-version/otherdir/COPYRIGHT, and finally COPYRIGHT in the top-level build directory to the documentation directory.

Finally, you can list files and directories. In the case of our rpmproc.spec file, we’re adding /usr/bin/rpmproc and /etc/rpmproc.conf from our BuildRoot environment to the package. In this case, you do not need to specify the $RPM_BUILD_ROOT macro before every file or directory because RPM knows that this is the virtual root directory to use when adding files to the package. It internally adds that macro before every file.

You can specify a number of files in a particular directory by using something like this:
/usr/bin/*

RPM will expand the wildcard and include everything that matches.

Even more can be done in the %files section. We’ll finish looking at this section and the large number of options it provides in part four of this series.

Conclusion
You now know how to install files to your BuildRoot environment and a few methods to circumvent installation procedures that are not friendly to RPM packages. You’ve also learned how to clean up your system after building an RPM and the more common ways of building the file list that RPM requires in order to determine which files to include in the package.

In the next Daily Drill Down of this series, we’ll look at some of the more obscure commands in the %files section, as well as a very simple way to obtain the file listing information to create the %files section for packages that install a large number of files in many different locations. We’ll also look at maintaining an RPM ChangeLog and see some pre- and post-installation scripts that are useful when building RPM packages.

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 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.

1 comments
Wokawidget
Wokawidget

Hi, i'm building an RPM from a tarball and would like to include a man page in my deployment. Could you explain how one can declare a man page and then copy it to the /usr/local/share/man/man1 directory so that it can be accessed via the man command once the RPM has been installed

Editor's Picks