13.4. Macros: Helpful Shorthand for Package Builders

RPM does not support macros in the sense of ad-hoc sequences of commands being defined as a macro and executed by simply referring to the macro name.

However, there are two parts of RPM's build process that are fairly constant from one package to another, and they are the unpacking and patching of sources. Because of this, RPM makes two macros available to simplify these tasks:

  1. The %setup macro, which is used to unpack the original sources.

  2. The %patch macro, which is used to apply patches to the original sources.

These macros are used exclusively in the %prep script; it wouldn't make sense to use them anywhere else. The use of these macros is not mandatory — It is certainly possible to write a %prep script without them. But in the vast majority of cases they make life easier for the package builder.

13.4.1. The %setup Macro

As we mentioned above, the %setup macro is used to unpack the original sources, in preparation for the build. In its simplest form, the macro is used with no options and gets the name of the source archive from the source tag specified earlier in the spec file. Let's look at an example. The cdplayer package has the following source tag:

Source: ftp://ftp.gnomovision.com/pub/cdplayer/cdplayer-1.0.tgz
          

and the following %prep script:

%prep
%setup
          

In this simple case, the %setup macro expands into the following commands:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
          

As we can see, the %setup macro starts by changing directory into RPM's build area and removing any cdplayer build trees from previous builds. It then uses gzip to uncompress the original source (whose name was taken from the source tag), and pipes the result to tar for unpacking. The return status of the unpacking is tested. If sucessful, the macro continues.

At this point, the original sources have been unpacked. The %setup macro continues by changing directory into cdplayer's top-level directory. The two cd commands are an artifact of %setup's macro expansion. Finally, %setup makes sure every file in the build tree is owned by root and has appropriate permissions set.

But that's just the simplest way that %setup can be used. There are a number of other options that can be added to accomodate different situations. Let's look at them.

13.4.1.1. -n <name> — Set Name of Build Directory

In our example above, the %setup macro simply uncompressed and unpacked the sources. In this case, the tar file containing the original sources was created such that the top-level directory was included in the tar file. The name of the top-level directory was also identical to that of the tar file, which was in <name>-<version> format.

However, this is not always the case. Quite often, the original sources unpack into a directory whose name is different than the original tar file. Since RPM assumes the directory will be called <name>-<version>, when the directory is called something else, it's necessary to use %setup's -n option. Here's an example:

Assume, for a moment, that the cdplayer sources, when unpacked, create a top-level directory named cd-player. In this case, our %setup line would look like this:

%setup -n cd-player
            

and the resulting commands would look like this:

cd /usr/src/redhat/BUILD
rm -rf cd-player
gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cd-player
cd /usr/src/redhat/BUILD/cd-player
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

The results are identical to using %setup with no options, except for the fact that %setup now does a recursive delete on the directory cd-player (instead of cdplayer-1.0), and changes directory into cd-player (instead of cdplayer-1.0).

Note that all subsequent build-time scripts will change directory into the directory specified by the -n option. This makes -n unsuitable as a means of unpacking sources in directories other than the top-level build directory. In the upcoming example on Section 13.4.1.7, we'll show a way around this restriction.

A quick word of warning: If the name specified with the -n option doesn't match the name of the directory created when the sources are unpacked, the build will stop pretty quickly, so it pays to be careful when using this option.

13.4.1.5. -b <n> — Unpack The nth Sources Before Changing Directory

The -b option is used in conjunction with the source tag. Specifically, it is used to identify which of the numbered source tags in the spec file are to be unpacked.

The -b option requires a numeric argument matching an existing source tag. If a numeric argument is not provided, the build will fail:

# rpm -ba cdplayer-1.0.spec
* Package: cdplayer
Need arg to %setup -b
Build failed.
#
            

Remembering that the first source tag is implicitly numbered 0, let's see what happens when the %setup line is changed to %setup -b 0:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

That's strange. The sources were unpacked twice. It doesn't make sense, until you realize that this is why there is a -T option. Since -T disables the default source file unpacking, and -b selects a particular source file to be unpacked, the two are meant to go together, like this:

%setup -T -b 0
            

Looking at the resulting commands, we find:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/cdplayer-1.0.tgz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

That's more like it! Let's go on to the next option.

13.4.1.7. Using %setup in a Multi-source Spec File

If all these interrelated options seem like overkill for unpacking a single source file, you're right. The real reason for the various options is to make it easier to combine several separate source archives into a single, build-able entity. Let's see how they work in that type of environment.

For the purposes of this example, our spec file will have the following three source tags: [1]

source: source-zero.tar.gz
source1: source-one.tar.gz
source2: source-two.tar.gz
            

To unpack the first source is not hard; all that's required is to use %setup with no options:

%setup
            

This produces the following set of commands:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

If source-zero.tar.gz didn't include a top-level directory, we could have made one by adding the -c option:

%setup -c
            

which would result in:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
mkdir -p cdplayer-1.0
cd cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

Of course, if the top-level directory did not match the package name, the -n option could have been added:

%setup -n blather
            

which results in:

cd /usr/src/redhat/BUILD
rm -rf blather
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd blather
cd /usr/src/redhat/BUILD/blather
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

or

%setup -c -n blather
            

This results in:

cd /usr/src/redhat/BUILD
rm -rf blather
mkdir -p blather
cd blather
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd /usr/src/redhat/BUILD/blather
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

Now let's add the second source file. Things get a bit more interesting here. First, we need to identify which source tag (and therefore, which source file) we're talking about. So we need to use either the -a or -b option, depending on the characteristics of the source archive. For this example, let's say that -a is the option we want. Adding that option, plus a "1" to point to the source file specified in the source1 tag, we have:

%setup -a 1
            

Since we've already seen that using the -a or -b option results in duplicate unpacking, we need to disable the default unpacking by adding the -T option:

%setup -T -a 1
            

Next, we need to make sure that the top-level directory isn't deleted. Otherwise, the first source file we just unpacked would be gone. That means we need to include the -D option to prevent that from happening. Adding this final option, and including the now complete macro in our %prep script, we now have:

%setup
%setup -T -D -a 1
            

This will result in the following commands:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
cd /usr/src/redhat/BUILD
cd cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-one.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
            

So far, so good. Let's include the last source file, but with this one, we'll say that it needs to be unpacked in a subdirectory of cdplayer-1.0 called database. Can we use %setup in this case?

We could, if source-two.tgz created the database subdirectory. If not, then it'll be necessary to do it by hand. For the purposes of our example, let's say that source-two.tgz wasn't created to include the database subdirectory, so we'll have to do it ourselves. Here's our %prep script now:

%setup
%setup -T -D -a 1
mkdir database
cd database
gzip -dc /usr/src/redhat/SOURCES/source-two.tar.gz | tar -xvvf -
            

Here's the resulting script:

cd /usr/src/redhat/BUILD
rm -rf cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-zero.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
cd cdplayer-1.0
cd /usr/src/redhat/BUILD/cdplayer-1.0
chown -R root.root .
chmod -R a+rX,g-w,o-w .
cd /usr/src/redhat/BUILD
cd cdplayer-1.0
gzip -dc /usr/src/redhat/SOURCES/source-one.tar.gz | tar -xvvf -
if [ $? -ne 0 ]; then
  exit $?
fi
mkdir database
cd database
gzip -dc /usr/src/redhat/SOURCES/source-two.tar.gz | tar -xvvf -
            

The three commands we added to unpack the last set of sources were added to the end of the %prep script.

The bottom line to using the %setup macro is that you can probably get it to do what you want, but don't be afraid to tinker. And even if %setup can't be used, it's easy enough to add the necessary commands to do the work manually. Above all, make sure you use the --test option when testing your %setup macros, so you can see what commands they're translating to.

Next, let's look at RPM's second macro, %patch.

13.4.2. The %patch Macro

The %patch macro, as its name implies, is used to apply patches to the unpacked sources. In the following examples, our spec file has the following patch tag lines:

patch0: patch-zero
patch1: patch-one
patch2: patch-two
          

At its simplest, the %patch macro can be invoked without any options:

%patch
          

Here are the resulting commands:

echo "Patch #0:"
patch -p0  -s < /usr/src/redhat/SOURCES/patch-zero
          

The %patch macro nicely displays a message showing that a patch is being applied, then invokes the patch command to actually do the dirty work. There are two options to the patch command:

  1. The -p option, which directs patch to remove the specified number of slashes (and any intervening directories) from the front of any filenames specified in the patch file. In this case, nothing will be removed.

  2. The -s option, which directs patch to apply the patch without displaying any informational messages. Only errors from patch will be displayed.

How did the %patch macro know which patch to apply? Keep in mind that, like the source tag lines, every patch tag is numbered, starting at zero. The %patch macro, by default, applies the patch file named on the patch (or patch0) tag line.

13.4.2.5. An example of the %patch Macro in Action

Using the example patch tag lines we've used throughout this section, let's put together an example and look at the resulting commands. In our example, the first patch to be applied needs to have the root directory stripped. Its %patch macro will look like this:

%patch -p1
            

The next patch is to be applied to files in the software's lib subdirectory, so we'll need to add a cd command to get us there. We'll also need to strip an additional directory:

cd lib
%patch -P 1 -p2
            

Finally, the last patch is to be applied from the software's top-level directory, so we need to cd back up a level. In addition, this patch modifies some files that were also patched the first time, so we'll need to change the backup file extension:

cd ..
%patch -P 2 -p1 -b .last-patch
            

Here's what the %prep script (minus any %setup macros) looks like:

%patch -p1
cd lib
%patch -P 1 -p2
cd ..
%patch -P 2 -p1 -b .last-patch
            

And here's what the macros expand to:

echo "Patch #0:"
patch -p1  -s < /usr/src/redhat/SOURCES/patch-zero
cd lib
echo "Patch #1:"
patch -p2  -s < /usr/src/redhat/SOURCES/patch-one
cd ..
echo "Patch #2:"
patch -p1 -b .last-patch -s < /usr/src/redhat/SOURCES/patch-two
            

No surprises here. Note that the %setup macro leaves the current working directory set to the software's top-level directory, so our cd commands with their relative paths will do the right thing. Of course, we have environment variables available that could be used here, too.

Notes

[1]

Yes, the source tags should include a URL pointing to the sources.