RPMs are the key to a Red Hat based system. They make administration of packages simple elegant and very easy to maintain. Building RPMs is not a difficult task. If you maintain your software with packages rather than installing from source, your updates will be painless and you will be able to roll back to previous versions without pain also.
Ok, so you know you want to use rpms, how do you start?
The easiest way I think is to build an actual package. The example I usually do is tar, it's one of the easiest things to build.
Some notes before you proceed though, always create your RPMs as yourself or as a build user, never use root. Why, because when running as root you can place files into their final locations on the filesystem without knowing it (eg /usr/bin), this is strictly bad practice. The only things that should be placing files into folders like /usr/bin, /usr/sbin are packages, not package builds.
There are some other caveats to building packages, for now lets get started building a package. The first step is to download the source for the package we wish to build and start making a spec file for our package
[root@client11 ~]# useradd build [root@client11 ~]# su - build [build@client11 ~]$ wget http://ftp.gnu.org/gnu/tar/tar-1.22.tar.bz2 --20:37:37-- http://ftp.gnu.org/gnu/tar/tar-1.22.tar.bz2 Resolving ftp.gnu.org... 140.186.70.20 Connecting to ftp.gnu.org|140.186.70.20|:80... connected. HTTP request sent, awaiting response... 200 OK Length: 2094575 (2.0M) [application/x-tar] Saving to: `tar-1.22.tar.bz2' 100%[=======================================>] 2,094,575 2.39M/s in 0.8s 16:32:16 (2.39 MB/s) - `tar-1.22.tar.bz2' saved [2094575/2094575]The next thing I like to do is make a build directory for myself. On recent versions of fedora, rpm will build a directory called rpmbuild for you in your home directory. On older versions rpm will try and install files in /usr/src. It's probably better to just specify your build directory just in case. You can do that with the .rpmmacros file, the option to specify is %_topdir.
[build@client11 ~]$ mkdir rpmbuild [build@client11 ~]$ cd rpmbuild [build@client11 rpmbuild]$ mkdir -p BUILD RPMS/x86_64 SOURCES SPECS SRPMS [build@client11 rpmbuild]$ cd [build@client11 ~]$ cat .rpmmacros %_topdir /home/build/rpmbuild [build@client11 ~]$ rpmbuild -bash: rpmbuild: command not foundIf you haven't already done so, install the rpm-build rpm so you have access to the rpmbuild command.
[build@client11 ~]$ exit [root@client11 yum.repos.d]# yum install rpm-build Loading "installonlyn" plugin ... ============================================================================= Package Arch Version Repository Size ============================================================================= Installing: rpm-build x86_64 4.4.2-37.el5 Workstation 558 k ... Installed: rpm-build.x86_64 0:4.4.2-37.el5 Dependency Installed: elfutils.x86_64 0:0.125-3.el5 elfutils-libs.x86_64 0:0.125-3.el5 Complete! [root@client11 yum.repos.d]# su - build [build@client11 ~]$ rpmbuild rpmbuild: no spec files given for buildAs rpmbuild has indicated, we need to make a spec file, lets start making one now.
There are several sections of a spec file.
To start our spec file, we'll create the SPECS directory to contain our spec files.
[build@client11 ~]$ cd rpmbuild [build@client11 rpmbuild]$ mkdir SPECS [build@client11 rpmbuild]$ cd SPECSNow our package will be called tar, tar is "A GNU file archiving program". Like most GNU software it is released under the GPL. So we can put all that information into our spec file.
Summary: A GNU file archiving program Name: tar License: GPL
Version: 1.22
Source: http://ftp.gnu.org/gnu/tar/%{name}-%{version}.tar.bz2
In the source line instead of using tar and 1.22, we used the variables that we already defined of name and version. Any of the lines that you specify in your spec can be used as variables later in your spec file. The advantage to doing things this way is that when tar 1.23 comes out, you only have to change the version (assuming ftp.gnu.org names version 1.23 as tar-1.23.tar.bz2)
Next we need to identify the release of our package, since this is our initial build of the package, we'll set our release tag to 1. You could put in your name here or some other identifier. Dag Wiers sometimes has dag, Red Hat will put el[4|5] here, fedora will put f11 etc. If you aren't too grandiose, use a number.
Release: 1Next is a bit of housekeeping, we need to tell rpm what this package contains, is it System software, a Game, or an Office or Productivity package, etc. In this case we'll use Applications/Archiving
Group: Applications/Archiving
One important thing to set at this point is the BuildRoot, this is the location where files created by this rpm should live temporarily during the build process. You need this set in order to make clean rpms that don't touch the system on which they are building.
BuildRoot: /tmp/rpm-buildroot-%{name}-%{version}
This should be enough of a header, some other things you can add in the header are:
| Packager: Requires: |
Vendor: BuildRequires:+ |
[build@client11 ~]$ tar tjvf tar-1.22.tar.bz2 |grep README -rw-r--r-- gray/users 2524 2008-04-14 08:03:13 tar-1.22/tests/star/README -rw-r--r-- gray/users 9692 2007-06-27 09:30:31 tar-1.22/README [build@client11 ~]$ tar xjvf tar-1.22.tar.bz2 tar-1.22/README tar-1.22/README [build@client11 ~]$ cat tar-1.22/README README for GNU tar See the end of file for copying conditions. ... GNU `tar' saves many files together into a single tape or disk archive, and can restore individual files from the archive. It includes multivolume support, the ability to archive sparse files, automatic archive compression/decompression, remote archives and special features that allow `tar' to be used for incremental and full backups. This distribution also includes `rmt', the remote tape server. The `mt' tape drive control program is in the GNU `cpio' distribution. ...You end the %description section by starting another section (%pre, %post, %prep, %build), the usual layout is to do %prep next.
RPM provides a macro to expand archives, %setup. There are several options, without any options it will expand the archive and place the contents in the BUILD directory. I usually only use the quiet option (-q). Other options are occasionally necessary if the package you are building is nonconformist.
For our example spec file, the following will expand our archive and place the contents in the BUILD directory for us.
%prep %setup -qOur package will not build with this spec file, but we'll try anyway so we can see an error.
[build@client11 SPECS]$ rpmbuild -ba tar.spec error: File /home/build/rpmbuild/SOURCES/tar-1.22.tar.bz2: No such file or directoryWe need to move the source file we downloaded earlier into the SOURCES directory.
[build@client11 SPECS]$ mv ~/tar-1.22.tar.bz2 ../SOURCES [build@client11 SPECS]$ rpmbuild tar.spec [build@client11 SPECS]$[build@client11 SPECS]$ rpmbuild -bb tar.spec Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.21480 + umask 022 + cd /home/build/rpmbuild/BUILD + cd /home/build/rpmbuild/BUILD + rm -rf tar-1.22 + /usr/bin/bzip2 -dc /home/build/rpmbuild/SOURCES/tar-1.22.tar.bz2 + tar -xf - + STATUS=0 + '[' 0 -ne 0 ']' + cd tar-1.22 + exit 0 Checking for unpackaged file(s): /usr/lib/rpm/check-files %{buildroot} [build@client11 SPECS]$ As you can see, rpm ran bzip2 to uncompress the file then piped it through tar to extract the files. RPM then cd'd to the directory tar-1.22, which is the name of our rpm - version. This is important, it is the directory you start in when you are at the build stage. We'll write our %build section now.
%build %configure makeFor many packages, this is it. %configure is a macro that sends the appropriate defaults to configure. You can see what %configure expands to by stopping rpmbuild in the middle of the build and looking at the temp file in /var that rpm runs. Each section of the spec file is first parsed and expanded then cut up by rpm and placed into an executable file in /var/tmp. We'll run rpmbuild and look for a line that starts with Executing(%build). This will tell us the filename of the file in /var/tmp.
[build@client11 SPECS]$ rpmbuild -bb tar.spec
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.61921
+ umask 022
+ cd /home/build/rpmbuild/BUILD
+ cd /home/build/rpmbuild/BUILD
+ rm -rf tar-1.22
+ /usr/bin/bzip2 -dc /home/build/rpmbuild/SOURCES/tar-1.22.tar.bz2
+ tar -xf -
+ STATUS=0
+ '[' 0 -ne 0 ']'
+ cd tar-1.22
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.21166
+ umask 022
+ cd /home/build/rpmbuild/BUILD
+ cd tar-1.22
+ CFLAGS='-O2 -g'
+ export CFLAGS
+ CXXFLAGS='-O2 -g'
+ export CXXFLAGS
+ FFLAGS='-O2 -g'
+ export FFLAGS
+ ./configure --host=x86_64-redhat-linux-gnu --build=x86_64-redhat-linux-gnu --target=x86_64-redhat-linux-gnu --program-prefix= --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/usr/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib --libexecdir=/usr/libexec --localstatedir=/usr/var --sharedstatedir=/usr/com --mandir=/usr/man --infodir=/usr/info
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane...
[1]+ Stopped rpmbuild -bb tar.spec
[build@client11 SPECS]$ cat /var/tmp/rpm-tmp.21166
#!/bin/sh
RPM_SOURCE_DIR="/home/build/rpmbuild/SOURCES"
RPM_BUILD_DIR="/home/build/rpmbuild/BUILD"
RPM_OPT_FLAGS="-O2 -g"
RPM_ARCH="x86_64"
RPM_OS="linux"
export RPM_SOURCE_DIR RPM_BUILD_DIR RPM_OPT_FLAGS RPM_ARCH RPM_OS
RPM_DOC_DIR="/usr/doc"
export RPM_DOC_DIR
RPM_PACKAGE_NAME="tar"
RPM_PACKAGE_VERSION="1.22"
RPM_PACKAGE_RELEASE="1"
export RPM_PACKAGE_NAME RPM_PACKAGE_VERSION RPM_PACKAGE_RELEASE
set -x
umask 022
cd /home/build/rpmbuild/BUILD
cd tar-1.22
CFLAGS="${CFLAGS:--O2 -g}" ; export CFLAGS ;
CXXFLAGS="${CXXFLAGS:--O2 -g}" ; export CXXFLAGS ;
FFLAGS="${FFLAGS:--O2 -g}" ; export FFLAGS ;
./configure --host=x86_64-redhat-linux-gnu --build=x86_64-redhat-linux-gnu \
--target=x86_64-redhat-linux-gnu \
--program-prefix= \
--prefix=/usr \
--exec-prefix=/usr \
--bindir=/usr/bin \
--sbindir=/usr/sbin \
--sysconfdir=/usr/etc \
--datadir=/usr/share \
--includedir=/usr/include \
--libdir=/usr/lib \
--libexecdir=/usr/libexec \
--localstatedir=/usr/var \
--sharedstatedir=/usr/com \
--mandir=/usr/man \
--infodir=/usr/info
make
Ok, let rpmbuild complete or kill it, it won't do anything useful yet, because we didn't install any files, we need an %install section next.
Again rpm provides a great macro in this section, but you can use whatever scripts you want to move the files. The root directory for your fake filesystem is in the variable $RPM_BUILD_ROOT. Consider this a blank canvas. If you want to put a file in /usr/bin, you have to create $RPM_BUILD_ROOT/usr/bin in your %install section. In our example, we only need use the %makeinstall macro. This macro do a make install with appropriate options, including setting the prefix and installdir variables for you.
%install %makeinstallWhen we run rpmbuild this time, we should see the files being built, and then installed into $RPM_BUILD_ROOT.
[build@client11 SPECS]$ rpmbuild -bb tar.spec
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.14568
+ umask 022
+ cd /home/build/rpmbuild/BUILD
+ cd /home/build/rpmbuild/BUILD
+ rm -rf tar-1.22
+ /usr/bin/bzip2 -dc /home/build/rpmbuild/SOURCES/tar-1.22.tar.bz2
...
+ ./configure --host=x86_64-redhat-linux-gnu --build=x86_64-redhat-linux-gnu --target=x86_64-redhat-linux-gnu --program-prefix= --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/usr/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib --libexecdir=/usr/libexec --localstatedir=/usr/var --sharedstatedir=/usr/com --mandir=/usr/man --infodir=/usr/info
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
...
installing zh_TW.gmo as /tmp/tar-1.22/usr/share/locale/zh_TW/LC_MESSAGES/tar.mo
if test "tar" = "gettext-tools"; then \
/bin/mkdir -p /tmp/tar-1.22/usr/share/gettext/po; \
for file in Makefile.in.in remove-potcdate.sin quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot Makevars.template; do \
/usr/bin/install -c -m 644 ./$file \
/tmp/tar-1.22/usr/share/gettext/po/$file; \
done; \
for file in Makevars; do \
rm -f /tmp/tar-1.22/usr/share/gettext/po/$file; \
done; \
else \
: ; \
fi
make[1]: Leaving directory `/home/build/rpmbuild/BUILD/tar-1.22/po'
Making install in tests
make[1]: Entering directory `/home/build/rpmbuild/BUILD/tar-1.22/tests'
make[2]: Entering directory `/home/build/rpmbuild/BUILD/tar-1.22/tests'
make[2]: Nothing to be done for `install-exec-am'.
make[2]: Nothing to be done for `install-data-am'.
make[2]: Leaving directory `/home/build/rpmbuild/BUILD/tar-1.22/tests'
make[1]: Leaving directory `/home/build/rpmbuild/BUILD/tar-1.22/tests'
make[1]: Entering directory `/home/build/rpmbuild/BUILD/tar-1.22'
make[2]: Entering directory `/home/build/rpmbuild/BUILD/tar-1.22'
make[2]: Nothing to be done for `install-exec-am'.
make[2]: Nothing to be done for `install-data-am'.
make[2]: Leaving directory `/home/build/rpmbuild/BUILD/tar-1.22'
make[1]: Leaving directory `/home/build/rpmbuild/BUILD/tar-1.22'
+ exit 0
Checking for unpackaged file(s): /usr/lib/rpm/check-files /tmp/tar-1.22
error: Installed (but unpackaged) file(s) found:
/usr/bin/tar
/usr/info/tar.info
/usr/info/tar.info-1
/usr/info/tar.info-2
/usr/libexec/rmt
/usr/share/locale/bg/LC_MESSAGES/tar.mo
...
RPM build errors:
Installed (but unpackaged) file(s) found:
/usr/bin/tar
/usr/info/tar.info
/usr/info/tar.info-1
...
We haven't defined any files installed by this package, but our %install section created many files. We can look at them by looking in the BuildRoot directory.
[build@client11 SPECS]$ find /tmp/tar-1.22/ |head /tmp/tar-1.22/ /tmp/tar-1.22/usr /tmp/tar-1.22/usr/sbin /tmp/tar-1.22/usr/share /tmp/tar-1.22/usr/share/locale /tmp/tar-1.22/usr/share/locale/pl /tmp/tar-1.22/usr/share/locale/pl/LC_MESSAGES /tmp/tar-1.22/usr/share/locale/pl/LC_MESSAGES/tar.mo /tmp/tar-1.22/usr/share/locale/ky /tmp/tar-1.22/usr/share/locale/ky/LC_MESSAGESWe need to tell rpm what we want done with these files, we'll do that next.
I usually start with a default attribute setting and then look for files that should be marked as documentation (%doc). Files marked with %doc are placed in /usr/share/doc/%{name}-%{version}. Populating this directory with files is very useful to the users of your package, most problems can be solved by looking in the /usr/share/doc directory.
%files %defattr(-,root,root,-)%defattr sets the default permissions for files using octal notation. The first parameter is the file mode, the permissions applied to files, a '-' means to keep the existing permissions. The last parameter is the directory mode, you can omit this completely and just put %defattr(-,root,root). root,root are the file owner and group respectively.
I usually look in the BUILD directory at this point to see which files should be included in doc, the general rule is anything marked Readme, README, README.txt or similar plus any files that are all CAPS. If there is a doc or html directory, just include the whole directory.
[build@client11 SPECS]$ ls ../BUILD/tar-1.22/ ABOUT-NLS ChangeLog configure.ac lib NEWS rmt THANKS aclocal.m4 ChangeLog.1 COPYING m4 po scripts TODO AUTHORS config.hin doc Makefile.am PORTS src build-aux configure INSTALL Makefile.in README tests [build@client11 SPECS]$
In the above we should include ABOUT-NLS AUTHORS ChangeLog COPYING doc INSTALL NEWS PORTS README scripts THANKS TODO
%doc ABOUT-NLS AUTHORS ChangeLog COPYING doc INSTALL NEWS PORTS README scripts THANKS TODONext we look in the install directory for the files that were installed by our %makeinstall.
[build@client11 SPECS]$ pushd /tmp/rpm-buildroot-tar-1.22/ /tmp/rpm-buildroot-tar-1.22 ~/rpmbuild/SPECS [build@client11 rpm-buildroot-tar-1.22]$ cd usr [build@client11 usr]$ ls bin doc info libexec sbin share [build@client11 usr]$ ls doc/tar-1.22/ ABOUT-NLS ChangeLog doc NEWS README THANKS AUTHORS COPYING INSTALL PORTS scripts TODO [build@client11 usr]$ ls bin tar [build@client11 usr]$ ls libexec rmt ...Looking through the directory we see that the usr/doc directory has all the files we specified as %doc in it. tar itself is in usr/bin and rmt is in usr/libexec. There are some localisation files in share/locale that we should include. sbin is empty. We continue looking through directories until we come up with a files section and test it out. If we missed anything, rpm will be sure to tell us.
%files %defattr(-,root,root) %doc ABOUT-NLS AUTHORS ChangeLog COPYING doc INSTALL NEWS PORTS README scripts THANKS TODO /usr/bin/tar /usr/info/tar.* /usr/share/locale/*We omitted a file on purpose to see the error, running rpmbuild will fail because rmt is not listed.
RPM build errors:
Installed (but unpackaged) file(s) found:
/usr/libexec/rmt
If we now add rmt into our files, section, the rpm will build.
Checking for unpackaged file(s): /usr/lib/rpm/check-files /tmp/rpm-buildroot-tar-1.22 Wrote: /home/build/rpmbuild/RPMS/x86_64/tar-1.22-1.x86_64.rpmWe aren't finished though, we should really add a %changelog section to our spec file, so we can track the changes we make to it as the package is updated. But, before we do that, I should mention variables. There are many variables available to you when writing your spec files. Variables like %{name} and %{version} we've already seen, but there are variables for directories on the system. If you use these variables your rpm can more easily be ported to another rpm based system. Also, if your system ever decides to move directories you won't have to rewrite your spec file.
You can view all the variables available to you by running rpm --showrc |grep -- ^-14:. One important variable is _prefix, _prefix is used as a prefix for many of the other variables. On my system _prefix is /usr, on some systems it may be /opt or something different. The other 'usual suspects' when trying to use variables are _bindir, _datadir (which is /usr/share), _sbindir and _mandir. Using variables, our new files section is below.
%files
%defattr(-,root,root)
%doc ABOUT-NLS AUTHORS ChangeLog COPYING doc INSTALL NEWS PORTS README scripts THANKS TODO
%{_bindir}/tar
%{_infodir}/tar.*
%{_libexecdir}/rmt
%{_datadir}/locale/*
This version is more portable and maintainable, but also looks cooler and all your friends will be impressed by your use of variables.
%changelog * Tue Sep 22 2009 Thomas Howard UphillNow, when version 1.23 of tar is released, you would come back and insert a new entry before this one. The most recent modification should come at the top of the changelog (as with all good changelogs). Adding the version to your entry is useful later when you are trying to remember when you made a change to the spec file and for what version you did it.- 1.22 - initial build, example spec file
The spec file is complete and this package will install cleanly on any system. There are several other sections to a spec file that we did not cover. I'll briefly describe them next.
[build@client11 SPECS]$ rpmbuild -bs tar.spec Wrote: /home/build/rpmbuild/SRPMS/tar-1.22-1.src.rpmIf you are only building the source rpm, you don't need to bother with dependencies, so a good option here is to say nodeps.
[build@client11 SPECS]$ rpmbuild --nodeps -bs tar.spec Wrote: /home/build/rpmbuild/SRPMS/tar-1.22-1.src.rpmThe build options for rpmbuild are as follows
[build@client11 SPECS]$ rpmbuild --short-circuit -bc tar.specOr if you have just built a package and it fails because you missed a file, you can just verify the files section again after making your change.
[build@client11 SPECS]$ rpmbuild --short-circuit -bl tar.spec Processing files: tar-1.22-1 Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 Requires: libc.so.6()(64bit) libc.so.6(GLIBC_2.2.5)(64bit) libc.so.6(GLIBC_2.3)(64bit) libc.so.6(GLIBC_2.4)(64bit) librt.so.1()(64bit) librt.so.1(GLIBC_2.2.5)(64bit) rtld(GNU_HASH) Checking for unpackaged file(s): /usr/lib/rpm/check-files /tmp/rpm-buildroot-tar-1.22I don't think I'll have time to cover some advanced topics, but something we use a lot is --target. --target allows you to specify the architecture for which you are building. We typically build on x86_64 and then specify --target to build the i386 rpm. You need to use the program setarch in front of rpmbuild to achieve a true i386 package.
[build@client11 SPECS]$ setarch i386 rpmbuild --target i386 -bb tar.spec
Building target platforms: i386
Building for target i386
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.85806
...
configure: error: C compiler cannot create executables
See `config.log' for more details.
error: Bad exit status from /var/tmp/rpm-tmp.85806 (%prep)
RPM build errors:
Bad exit status from /var/tmp/rpm-tmp.85806 (%prep)
There's a lot more to that problem though...you need a full i386 system installed so that you can compile i386 binaries on an x86_64 system. That means compilers, libraries, etc. The solution to that problem is beyond scope but I can summarize it with one word, mock.
It's doubtful you'll need any other options to rpmbuild, read the man page, but the rest are all margin case options.