Packaging Linux Software with RPM by Design
When writing software for Linux systems, it's often wise to plan on packaging your software using RPM. Having an easily installable package for your software lets you install and maintain your software using a suite of automated tools. Many software developers don't plan on building RPMs from their sources, but instead rely on operating system distribution vendors and third-party enthusiasts to package their software. This is possible thanks to the flexible nature of RPM's build system, but if you plan ahead, you can have your own build scripts produce RPMs, gaining more control over the packaging in the process.
This two-part introductory series of articles from Red Hat Magazine go over the basics of what RPM is, how to use it, and how to package software using RPM build scripts:
http://www.redhat.com/magazine/001nov04/features/betterliving/
http://www.redhat.com/magazine/002dec04/features/betterliving-part2/
The references I come back to time after time for building RPMs are the Fedora RPM guide and Maximum RPM. Maximum RPM is getting a bit out of date, but it is what I originally learned from.
People who build RPMs frequently set up a sandbox in their home directory, as many projects recommend, such as the JPackage and SIPFoundry packaging efforts do. This is a good practice, but you can't always rely on people to set up this RPM sandbox themselves. Fortunately, it is possible to do this as part of your own build scripts.
Typically when building software you add an "rpm" target to your build tool, so that you can do "make rpm" or "ant rpm" and have it build the RPM. This target should set up a minimal self-contained RPM build environment under the "build" directory and invoke RPM It's desirable to have a self-contained RPM build environment for your project, rather than relying on the system /usr/src/redhat RPM build environment.
You want the RPM build to be a part of your "dist" target, the one that builds a release archive of both source code and executable outputs. The basic flow would go:
1. Build the software in your "all" target
2. Package it in the "dist" target into a .tar.gz file
3. Have "dist" start the "rpmbuild" command with appropriate switches to make it fly.
Here's a fragment of an Ant build script that does this:
<target name="rpm" depends="dist" description="Build a JPackage RPM">
<mkdir dir="${rpm.build.root}/SOURCES" />
<mkdir dir="${rpm.build.root}/SOURCES" />
<mkdir dir="${rpm.build.root}/SPECS" />
<mkdir dir="${rpm.build.root}/BUILD" />
<mkdir dir="${rpm.build.root}/RPMS" />
<mkdir dir="${rpm.build.root}/RPMS/noarch" />
<mkdir dir="${rpm.build.root}/RPMS/i386" />
<mkdir dir="${rpm.build.root}/RPMS/x86_64" />
<mkdir dir="${rpm.build.root}/SRPMS" />
<copy file="${tarball}" todir="${rpm.build.root}/SOURCES"/>
<copy file="${build.dir}/${project}.spec" todir="${rpm.build.root}/SPECS"/>
<rpm specFile="${project}.spec" topDir="${rpm.build.root}" command="-ba" />
</target>
You'd set rpm.build.root to a subdirectory of your build directory.
Although it's not very well documented, you can set the _topdir and _tmppath RPM variables on the rpmbuild command line. Here's a partial GNU Makefile adapted form a working project that builds an RPM that does this:
SHELL = /bin/bash
PROJECT = mod_pki
VERSION = $(shell grep Version $(PROJECT).spec | cut -c 17-)
BUILD=build
TARFILE = $(BUILD)/$(PROJECT)-$(VERSION).tar.gz
RPM_BUILD_ROOT=$(BUILD)/rpmbuild
SPEC=$(PROJECT).spec
SPEC_BUILD=$(RPM_BUILD_ROOT)/SPECS/$(PROJECT).spec
all:
# build the software here
dist: clean tar rpm
tar:
# create the TARFILE here
clean:
rm -rf $(BUILD)
# do whatever else you need to build stuff
rpm:
mkdir -p $(RPM_BUILD_ROOT); \
mkdir -p $(RPM_BUILD_ROOT)/SOURCES; \
mkdir -p $(RPM_BUILD_ROOT)/SPECS; \
mkdir -p $(RPM_BUILD_ROOT)/BUILD; \
mkdir -p $(RPM_BUILD_ROOT)/RPMS; \
mkdir -p $(RPM_BUILD_ROOT)/RPMS/noarch; \
mkdir -p $(RPM_BUILD_ROOT)/RPMS/i386; \
mkdir -p $(RPM_BUILD_ROOT)/RPMS/x86_64; \
mkdir -p $(RPM_BUILD_ROOT)/SRPMS; \
mkdir -p $(RPM_BUILD_ROOT)/tmp; \
cp $(TARFILE) $(RPM_BUILD_ROOT)/SOURCES; \
cp $(SPEC) $(RPM_BUILD_ROOT)/SPECS; \
rpmbuild -ba --define "_topdir $(PWD)/$(RPM_BUILD_ROOT)" --define "_tmppath $(PWD)/$(RPM_BUILD_ROOT)/tmp" $(SPEC_BUILD)

