RPMs - How You Can Build Them!


I am planning a book on creating RPMs, and one part I want to cover is this (arguably "impure") way of creating RPMs very quickly. I just thought I'd put it out there and see if it's useful to anyone, so please, mail me or send feedback online with your thoughts on anything to do with making RPMs (or .DEBs, etc).


  • 1. Creating a Simple Binary RPM
  • 2. Creating a Useful Binary RPM
    • 2.1 Licensing
    • 2.2 Building the RPM again
    • 2.3 Adding Some Files
    • 2.4 Placing the Files
    • 2.5 Building the Package (Take Three)
    • 2.6 Don't Clean Up!

1. Creating a Simple Binary RPM

There are two types of RPM: a Source RPM, known as an SRPM, and a Binary RPM, which is the type of RPM that most people are familiar with. The Binary RPM does not have to include any binaries, or even any kind of executable code, it is only called Binary because RPM comes from a tradition of creating executable binaries from source code.

The Binary RPM is the simplest type to create, and we shall start off with the simplest of all RPMs - the empty RPM with no files. To get started, create the $HOME/rpmbuild/SPECS directory, and create a my-first-rpm.spec file inside it:

[steve@packaging ~]$ cd
[steve@packaging ~]$ mkdir rpmbuild
[steve@packaging ~]$ cd rpmbuild
[steve@packaging rpmbuild]$ mkdir SPECS
[steve@packaging rpmbuild]$ cd SPECS
[steve@packaging SPECS]$ vi my-first-rpm.spec
Now, add this text into the file:
Name:           my-first-rpm
Release:        1
Summary:        This is my first ever RPM
License:        none
Version:        1.0

%description
My First RPM

%files

This is the bare minimum necessary to build an RPM file. We can use this as a template to build upon. It's of no actual use in itself, but we can build it up to see how we create a more complete spec file, and this process should be useful when you need to create one of your own in real life. The first section defines the name, version, license and so on of the package, followed by the mandatory %description and %files sections. Our %files section is empty, as we have no files (yet), and the %description is a simple placeholder.

With this my-first-rpm.spec file, we are ready to create our first ever RPM file. We do this with the rpmbuild command, passing it the -bb switch (bb for "Build Binary"), and finally, the name of our my-first-rpm.spec file:

     1	[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm.spec
     2	Processing files: my-first-rpm-1.0-1.x86_64
     3  Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-1.x86_64
     4	warning: Could not canonicalize hostname: packaging.sgpit.com
     5	Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm
     6	Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.oGgL83
     7	+ umask 022
     8	+ cd /home/steve/rpmbuild/BUILD
     9	+ /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-1.x86_64
    10	+ exit 0
    11	[steve@packaging SPECS]$

I've added line numbers to the output, so that we can more easily discuss what happens here. On line 1, we run the rpmbuild command. Line 2 confirms that it is processing the RPM, and shows the full name of the RPM, as derived from the metadata provided in the .spec file.

On line 3, rpmbuild checks to see whether there are any additional files in the BUILDROOT directory which were not mentioned in the .spec file. We will come to this later, when we start adding files to our RPM, but this is a sanity check to ensure that nothing gets missed.

Line 4 is a warning that rpmbuild can't resolve the server's hostname (packaging.sgpit.com, in this case) - we can easily enough resolve this by adding an entry in /etc/hosts. You may wonder why it even tries to resolve it, but every RPM includes the name of the host that created it, and rpmbuild tries to find the full DNS name of the server for this purpose. This metadata in the RPM can be useful for lots of reasons, but it's worth bearing in mind that when you create a package, whether for internal use, for a single customer, or for public distribution, all recipients of the RPM file will also know your hostname. So don't call your machine "customers.are.stupid.example.com", for example!

Line 5 confirms that the RPM file has been successfully created, and tells you where the created RPM file can be found.

The rest of the output, lines 6-10, show the execution of a small shell script that rpmbuild created. Since we didn't define a %clean section in our .spec file, rpmbuild creates one which deletes everything from the BUILDROOT directory. Again, we will come to this later, but it is worth being aware that your carefully-crafted contents will be deleted automatically unless you tell it not to. A dangerous side-effect, for the unwary. But we don't have any files (yet), so we don't care (yet)!

We can now inspect our RPM file. The rpm -qip command is a query for information of the uninstalled package file.

[steve@packaging SPECS]$ rpm -qip ../RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm
Name        : my-first-rpm                 Relocations: (not relocatable)
Version     : 1.0                               Vendor: (none)
Release     : 1                             Build Date: Sat 03 Jan 2015 11:29:58 GMT
Install Date: (not installed)               Build Host: packaging.sgpit.com
Group       : Unspecified                   Source RPM: my-first-rpm-1.0-1.src.rpm
Size        : 0                                License: none
Signature   : (none)
Summary     : This is my first ever RPM
Description :
My First RPM
[steve@packaging SPECS]$ 

Congratulations! That was a lot of reading and typing, for an entirely useless RPM file, but you have already crossed the biggest hurdle in creating an RPM. Just getting all of this preparation work out of the way is the single biggest obstacle to creating your own bespoke, lovingly hand-crafted RPM files. There's no stopping you now!

2. Creating a Useful Binary RPM

Now that you can create an RPM file, you can start adding files to it, and start to specify all sorts of impressive-looking metadata to the package. We will start with the metadata. Lots of the rpm -qip output shown at the end of the previous section looks like the RPM was created by somebody who was barely capable of making an RPM at all, and who stopped as soon as they got something that even vaguely worked. We can do so much better than that!

Down the first column, we're pretty much okay. We can specify what group of packages this belongs to (see Appendix A for a list of suggested groups). We've not signed our RPM, but we will come to that later.

The second column is more administrative information, including licensing information. This subject is worthy of a little digression:

2.1 Licensing

In the second column, we have not specfied a Vendor or a License. It is also possible to specify a Packager, if that is different from the Vendor. For the first version of the package, we did actually specify a License of "none" because rpmbuild refuses to create the package if you don't put anything. It doesn't make sense to distribute anything without a license. The GPL and BSD licenses are the most common in a typical GNU/Linux distribution. You can just as easily specify a proprietary software license here, too. Normally just the word "Proprietary" is used in this case. Whatever the license, it is often useful to include a licensing text file in the package. On Red Hat based systems, this would typically be in /usr/share/doc/%{name}-%{version}/LICENSE. For example, the ethtool package on Red Hat 6 comes with these files:

[steve@packaging SPECS]$ rpm -ql ethtool
/sbin/ethtool
/usr/sbin/ethtool
/usr/share/doc/ethtool-3.5
/usr/share/doc/ethtool-3.5/AUTHORS
/usr/share/doc/ethtool-3.5/COPYING
/usr/share/doc/ethtool-3.5/ChangeLog
/usr/share/doc/ethtool-3.5/LICENSE
/usr/share/doc/ethtool-3.5/NEWS
/usr/share/doc/ethtool-3.5/README
/usr/share/man/man8/ethtool.8.gz

As well as the binary itself (/usr/sbin/ethtool is just a link to /sbin/ethtool, in case you were wondering) and the ethtool.8.gz man page, there are a fairly typical set of text files in /usr/share/doc to accompany the program. So the License field in the spec file does not need to go into great detail, but just let somebody know what class of license the package is distributed under.

2.2 Building the RPM again

For a second version of our RPM, let's make a copy of the spec file, and add some more detail. The actual name of the spec file itself doesn't matter - I tend to update its name with each new release, like this:

[steve@packaging SPECS]$ cp my-first-rpm.spec my-first-rpm-2.spec
[steve@packaging SPECS]$ vi my-first-rpm-2.spec

We should also add a few lines of description - this is normally a paragraph or so, describing the package in more detail than the one-line format allows. Because this book is Documentation, I will also say that our RPM itself is part of the Documentation group of packages. I will also add a license, and a Vendor. I have increased the Release from 1 to 2. The Release should always be an integer, and it goes up whenever you make a new release from the same version of the software. If the software itself changes, then the Version field would need to be updated, too. You can think of Releases and Versions like this:

EventVersionReleaseRPM Name
First Release of New Software1.01name-1.0-1
Software is updated to version 1.11.11name-1.1-1
Version 1.1 is repackaged with a better description1.12name-1.1-2
Software is updated to version 1.21.21name-1.2-1
Version 1.2 is repackaged with updated Dependencies1.22name-1.2-2
Version 1.2 is repackaged with updated symbolic links, no code change1.23name-1.2-3
Software is updated to version 1.31.31name-1.3-1

The resulting my-first-rpm-2.spec file now looks like this (I have also added the hostname to /etc/hosts, so we do not get the warning when we build the new RPM):

Name:           my-first-rpm
Release:        2
Summary:        This is my first ever RPM
License:        GPLv2
Version:        1.0
Vendor:         Steve Parker
Group:          Documentation

%description
This is my first RPM package. I can describe it more thoroughly here. It is
normal to format these sections as 80 columns wide, so that it can easily
be read on any screen.

%files

So we are ready to build the second release of our package. Back to rpmbuild:

[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-2.spec
Processing files: my-first-rpm-1.0-2.x86_64
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-2.x86_64
Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.MdKzNp
+ umask 022
+ cd /home/steve/rpmbuild/BUILD
+ /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-2.x86_64
+ exit 0
[steve@packaging SPECS]$ 

When we inspect the new package, the presentation looks much better now:

[steve@packaging SPECS]$ rpm -qip ../RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm
Name        : my-first-rpm                 Relocations: (not relocatable)
Version     : 1.0                               Vendor: Steve Parker
Release     : 2                             Build Date: Sat 03 Jan 2015 14:02:17 GMT
Install Date: (not installed)               Build Host: packaging.sgpit.com
Group       : Documentation                 Source RPM: my-first-rpm-1.0-2.src.rpm
Size        : 0                                License: GPLv2
Signature   : (none)
Summary     : This is my first ever RPM
Description :
This is my first RPM package. I can describe it more thoroughly here. It is
normal to format these sections as 80 columns wide, so that it can easily
be read on any screen.
[steve@packaging SPECS]$ 

2.3 Adding Some Files

The RPM is still pretty useless, as it still contains no files. Create a small shell script, called my-first-program:

#!/bin/bash
unset YOUR_NAME
CONFIG=/etc/my-first-program.conf
[ -f $CONFIG ] && . $CONFIG

[ ! -z "$YOUR_NAME" ] && echo -en "Hello, ${YOUR_NAME}."
echo "This is a very simple shell script as an example of a packaged program."
Like many programs, it uses a configuration file:
# This is a configuration file for my-first-program.
# You can define the YOUR_NAME variable here, for example:
# YOUR_NAME=Fred
YOUR_NAME=

It also has some documentation to go with it, a README file:

This is a very simple program. 
If YOUR_NAME is defined in /etc/my-first-program.conf, then it will say Hello to you by name. 
It will then display a message.

As this is a GPLv2 licensed program, I have also taken the COPYING and LICENSE files that we noticed in the ethtool package earlier. This is a requirement for GPL licensed software; if you are packaging up some existing software, it should come with whatever license files it required. If you are packaging up your own software, you should provide whatever documentation is necessary for the license you choose.

The third iteration of our spec file looks like this:

Name:           my-first-rpm
Release:        3
Summary:        This is my first ever RPM
License:        GPLv2
Version:        1.0
Vendor:         Steve Parker
Group:          Documentation

%description
This is my first RPM package. I can describe it more thoroughly here. It is
normal to format these sections as 80 columns wide, so that it can easily
be read on any screen.

%files
/usr/bin/my-first-program
%config /etc/my-first-program.conf
%doc %name-%version/README
%doc %name-%version/COPYING
%doc %name-%version/LICENSE

We shall discuss the %config and %doc directives in the "Building the Package" section below, though I am sure you can guess what they mean!

2.4 Placing the Files

To get these files included in the RPM, we need to put them into the $HOME/rpmbuild/BUILDROOT directory. At least, that is true of the first two files; we will look at the documentation files in a moment.

I have currently got the files in a directory called my-first-program in my home directory. We shall need to copy them from where they are, into a directory called BUILDROOT/%name-%version-%release.%arch. In this case, that is BUILDROOT/my-first-rpm-1.0-3.x86_64. We create a mockup of the root filesystem in there, which will be used to create the package:

[steve@packaging SPECS]$ ls ~/my-first-program/
COPYING  LICENSE  my-first-program  my-first-program.conf  README
[steve@packaging SPECS]$ mkdir -p ../BUILDROOT/my-first-rpm-1.0-3.x86_64/etc
[steve@packaging SPECS]$ mkdir -p ../BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin
[steve@packaging SPECS]$ cp ~/my-first-program/my-first-program.conf ../BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/
[steve@packaging SPECS]$ cp ~/my-first-program/my-first-program ../BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/

The documentation files are a little different, because we didn't specify where in the filesystem they should go - "%{name}-%{version}/README" is a relative path, not an absolute path (it does not begin with "/"). An operating system could choose to put these files wherever it likes, and it makes no difference to our package, so we can just label them as documentation, using the %doc directive, specify only a relative path, and let RPM decide where to put them. On a default Red Hat based system, this would be /usr/share/doc, but it could be anywhere. So for these files, we put them into the BUILD directory, since that is where the packager would expect to find them:

[steve@packaging SPECS]$ mkdir ../BUILD/my-first-rpm-1.0
[steve@packaging SPECS]$ cp ~/my-first-program/LICENSE ../BUILD/my-first-rpm-1.0
[steve@packaging SPECS]$ cp ~/my-first-program/COPYING ../BUILD/my-first-rpm-1.0
[steve@packaging SPECS]$ cp ~/my-first-program/README ../BUILD/my-first-rpm-1.0

The whole tree currently looks like this (we are still in the rpmbuild/SPECS directory). I have marked our files to be installed in bold text, the other files are from the previous builds:

[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README
/home/steve/rpmbuild/SPECS/my-first-rpm.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-2.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-3.spec
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/my-first-program
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/my-first-program.conf
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm
[steve@packaging SPECS]$ 

2.5 Building the Package (Take Three)

We are now ready to build our package. There is quite a lot more output than before, so again I will number the lines so that we can discuss it in detail:

     1	[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-3.spec
     2	Processing files: my-first-rpm-1.0-3.x86_64
     3	Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.ryqrzS
     4	+ umask 022
     5	+ cd /home/steve/rpmbuild/BUILD
     6	+ DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
     7	+ export DOCDIR
     8	+ rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
     9	+ /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
    10	+ cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
    11	+ cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
    12	+ cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
    13	+ exit 0
    14	Provides: config(my-first-rpm) = 1.0-3
    15	Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
    16	Requires: /bin/bash
    17	Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64
    18	Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm
    19	Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.GpyOXx
    20	+ umask 022
    21	+ cd /home/steve/rpmbuild/BUILD
    22	+ /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64
    23	+ exit 0
    24	[steve@packaging SPECS]$

The first thing that rpmbuild does, on lines 3-13, is to process the %doc directive that we have given it. It executes a small shell script that it created, which copies our document files into the BUILDROOT tree for us. We had to this for ourselves on the /usr/bin and /etc files, but the %doc directive causes this to be done for us. In the spec file we specified %name-%version, as that is the usual convention for the system documentation directory (i.e.,/usr/share/doc/my-first-rpm-1.0/).

Line 14 shows that we have given it a %config directive for the /etc/my-first-program.conf file. Telling rpm that this is a configuration file allows it to give it special treatment during upgrades. See TODO FIXME ??? Later in doc??? for more information on how rpm treats configuration files. Line 15 is about the features available in our current version of rpm - CompressedFileNames means that file names are stored as (dirName,BaseName,dirIndex) rather than as a regular path, and PayloadFilesHavePrefix means that files are stored with a "./" prefix. None of that is terribly important to us, however. TODO FIXME Link to www.redhat.com/archives/rpm-list/2000-October/msg00209.html

Line 16 shows that rpmbuild has inspected the actual content of the files that we are packaging. It noticed that the my-first-program script requires the /bin/bash executable - if the user did a "yum install my-first-rpm" then yum would dig out the bash package too. If they just did a "rpm -i my-first-rpm-1.0-3.x86_64.rpm" and bash wasn't installed, it would refuse to install my-first-rpm. This automated process is not always foolproof, but it can be a useful automated tool for picking up such dependencies.

The remaining lines are the same as before, including the dangerous %clean section. Running the find command again, we see that rpmbuild has "helpfully" cleaned out our BUILDROOT (though not the BUILD) directory:

[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README
/home/steve/rpmbuild/SPECS/my-first-rpm.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-2.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-3.spec
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm
[steve@packaging SPECS]$ 

2.6 Don't Clean Up!

Since we carefully put our files there, and in real life may find that we need to create the RPM a few times before we get it exactly how we want it, this behaviour can be a real pain. We can stop it from doing this by adding our own, blank, %clean directive into the spec file. Let's update the my-first-rpm-3.spec file one final time:

Name:           my-first-rpm
Release:        3
Summary:        This is my first ever RPM
License:        GPLv2
Version:        1.0
Vendor:         Steve Parker
Group:          Documentation

%description
This is my first RPM package. I can describe it more thoroughly here. It is
normal to format these sections as 80 columns wide, so that it can easily
be read on any screen.

%clean
echo "Not cleaning up in this case."

%files
/usr/bin/my-first-program
%config /etc/my-first-program.conf
%doc %name-%version/README
%doc %name-%version/COPYING
%doc %name-%version/LICENSE

We can then repopulate our carefully-configured fake root directory, and create the package again:

[steve@packaging SPECS]$ mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc
[steve@packaging SPECS]$ mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin
[steve@packaging SPECS]$ cp ~/my-first-program/my-first-program.conf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc
[steve@packaging SPECS]$ cp ~/my-first-program/my-first-program /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin
[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-3.spec
Processing files: my-first-rpm-1.0-3.x86_64
Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.TuSj1K
+ umask 022
+ cd /home/steve/rpmbuild/BUILD
+ DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ export DOCDIR
+ rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0
+ exit 0
Provides: config(my-first-rpm) = 1.0-3
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires: /bin/bash
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64
Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.wyKIt5
+ umask 022
+ cd /home/steve/rpmbuild/BUILD
+ echo 'Not cleaning up in this case.'
Not cleaning up in this case.
+ exit 0

Now when we look, we can see that our fake root filesystem is still there - it even shows the copied %doc files, which were put there automatically for us:

[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING
/home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README
/home/steve/rpmbuild/SPECS/my-first-rpm.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-2.spec
/home/steve/rpmbuild/SPECS/my-first-rpm-3.spec
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/my-first-program
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/LICENSE
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/COPYING
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/README
/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/my-first-program.conf
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm
/home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm
[steve@packaging SPECS]$ 

It is worth saying that although here we take steps to disable the feature, automatically cleaning out the BUILDROOT directory is the right thing for rpmbuild to do, since it assumes that we are using all of its features, and we wouldn't want artifacts left hanging around from previous builds. What we are doing here is a bit of a naughty shortcut for creating RPMs, but this method is Good Enough™ for lots of uses, and I find that a spec file like my-first-rpm-3.spec is very nearly a useful template to have around for quickly putting an RPM package together. Two more small tweaks will make it more genuinely useful.

Permissions

We have not specified any permissions on our files. It is possible to stipulate the desired permissions in our BUILDROOT directory, but it's not desirable (and maybe not even possible) to create root-owned files in your home directory, let alone root-owned SUID files, or other special permissions that you might want your files to be installed with. You may need files to be owned by a particular user, but that user account does not exist on your packaging machine. So your spec file can be used to specify any permissions required.

As standard, the files will be packaged exactly as they are found in your BUILDROOT directory. That is fine, as far as it goes, but the files are likely to be owned by your own username, which is unlikely to be what you want.

You can specify the default permissions to use, for when you don't give any direct requirements, with the %defattr parameter, just after the %files directive. You can then override this with the %attr directive for any given file. For example, to set the default permissions as 0755, owner root, group root, but have the configuration file with permissions 0644, you can use this %files section:

%files
%defattr(0755,root,root)
/usr/bin/my-first-program
%attr(0644,root,root) %config /etc/my-first-program.conf
%doc %name-%version/README
%doc %name-%version/COPYING
%doc %name-%version/LICENSE

Copy the my-first-rpm-3.spec file to my-first-rpm-4.spec, update the Release number to 4, and copy the BUILDROOT to have a directory name which matches the new Release number:

[steve@packaging SPECS]$ cp my-first-rpm-3.spec my-first-rpm-4.spec
[steve@packaging SPECS]$ vi my-first-rpm-4.spec (just to update the Release)
[steve@packaging SPECS]$ cd ../BUILDROOT/
[steve@packaging BUILDROOT]$ cp -r my-first-rpm-1.0-3.x86_64/ my-first-rpm-1.0-4.x86_64/

ChangeLog

The final thing to add to our simple spec file is a ChangeLog. This is a useful piece of documentation to add to an RPM, as it can track what changes were made, why, when, and by whom. RPM is rather picky about the formatting of Changelog entries, but this will suffice for now:

%changelog
* Mon Jan 5 2015 Steve Parker 
- Bug fixes and improvements
* Sun Jan 4 2015 Steve Parker 
- Initial release.

Append this to the end of the spec file, and we should have a presentable RPM file:

[steve@packaging SPECS]$ cat my-first-rpm-4.spec
Name:           my-first-rpm
Release:        4
Summary:        This is my first ever RPM
License:        GPLv2
Version:        1.0
Vendor:         Steve Parker
Group:          Documentation

%description
This is my first RPM package. I can describe it more thoroughly here. It is
normal to format these sections as 80 columns wide, so that it can easily
be read on any screen.

%clean
echo "Not cleaning up in this case."

%files
%defattr(0755,root,root)
/usr/bin/my-first-program
%attr(0644,root,root) %config /etc/my-first-program.conf
%doc %name-%version/README
%doc %name-%version/COPYING
%doc %name-%version/LICENSE

%changelog
* Mon Jan 5 2015 Steve Parker 
- Bug fixes and improvements
* Sun Jan 4 2015 Steve Parker 
- Initial release.

Create the package, as before:

[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-4.spec
Processing files: my-first-rpm-1.0-4.x86_64
Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.1dPZUt
+ umask 022
+ cd /home/steve/rpmbuild/BUILD
+ DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ export DOCDIR
+ rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0
+ exit 0
Provides: config(my-first-rpm) = 1.0-4
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires: /bin/bash
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64
Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-4.x86_64.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.h0iVIC
+ umask 022
+ cd /home/steve/rpmbuild/BUILD
+ echo 'Not cleaning up in this case.'
Not cleaning up in this case.
+ exit 0
[steve@packaging SPECS]$ 

If you have root access, you can now upgrade to Release 4 of the RPM, and inspect the new permissions and Change Log.

TODO FIXME: Note: For more information on Linux file permission modes, see this image (via https://twitter.com/brutelogic/status/533207014630227969)

[steve@packaging SPECS]$ sudo rpm -Uvh ../RPMS/x86_64/my-first-rpm-1.0-4.x86_64.rpm
Preparing...                ########################################### [100%]
   1:my-first-rpm           ########################################### [100%]
[steve@packaging SPECS]$ ls -l /usr/bin/my-first-program /usr/share/doc/my-first-rpm-1.0/ /etc/my-first-program.conf
-rw-r--r--. 1 root root  140 Jan  5 20:26 /etc/my-first-program.conf
-rwxr-xr-x. 1 root root  227 Jan  5 20:26 /usr/bin/my-first-program

/usr/share/doc/my-first-rpm-1.0/:
total 28
-rwxr-xr-x. 1 root root 17992 Jan  3 15:35 COPYING
-rwxr-xr-x. 1 root root   109 Jan  3 15:35 LICENSE
-rwxr-xr-x. 1 root root   157 Jan  3 15:20 README
[steve@packaging SPECS]$ rpm -q --changelog my-first-rpm
* Mon Jan 05 2015 Steve Parker 
- Bug fixes and improvements

* Sun Jan 04 2015 Steve Parker 
- Initial release.

[steve@packaging SPECS]$ 

That should be enough for a very simple, buy presentable binary RPM package of some prebuilt code. There is still a lot more that RPM can do, such as pre- and post-installation scripts, and patching and compiling source code, but this is in practice often enough for basic sysadmin type scenarios.






A Draft of a Document I'm Working On - Please send feedback!
Share on Twitter Share on Facebook Share on LinkedIn Share on Identi.ca Share on StumbleUpon