Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active November 12, 2023 16:47
Show Gist options
  • Save Integralist/0cad5acc795175e53393 to your computer and use it in GitHub Desktop.
Save Integralist/0cad5acc795175e53393 to your computer and use it in GitHub Desktop.

See my working application (and additional notes) here:

https://github.com/integralist/simple-rpm

Other information that led to the above repository, can be found below

References

Folders

Macro Name Name Location Purpose
%_specdir Specification directory ~/rpmbuild/SPECS RPM specifications (.spec) files
%_sourcedir Source directory ~/rpmbuild/SOURCES Pristine source package (e.g. tarballs) and patches
%_builddir Build directory ~/rpmbuild/BUILD Source files are unpacked and compiled in a subdirectory underneath this.
%_buildrootdir Build root directory ~/rpmbuild/BUILDROOT Files are installed under here during the %install stage.
%_rpmdir Binary RPM directory ~/rpmbuild/RPMS Binary RPMs are created and stored under here.
%_srcrpmdir Source RPM directory ~/rpmbuild/SRPMS Source RPMs are created and stored here.

Setup

mkdir -p ~/rpmbuild/{RPMS,SRPMS,BUILD,SOURCES,SPECS,tmp}

cat <<EOF >~/.rpmmacros
%_topdir   %(echo $HOME)/rpmbuild
%_tmppath  %{_topdir}/tmp
EOF

cd ~/rpmbuild

Tarball your project

mkdir foo-1.0
mkdir -p foo-1.0/usr/bin
mkdir -p foo-1.0/etc/foo
install -m 755 foo foo-1.0/usr/bin
install -m 644 foo.conf foo-1.0/etc/foo/

tar -zcvf foo-1.0.tar.gz foo-1.0/

Copy to SOURCES

cp foo-1.0.tar.gz SOURCES/

cat <<EOF > SPECS/foo.spec
# Don't try fancy stuff like debuginfo, which is useless on binary-only
# packages. Don't strip binary too
# Be sure buildpolicy set to do nothing
%define        __spec_install_post %{nil}
%define          debug_package %{nil}
%define        __os_install_post %{_dbpath}/brp-compress

Summary: A very simple toy bin rpm package
Name: foo
Version: 1.0
Release: 1
License: GPL+
Group: Development/Tools
SOURCE0 : %{name}-%{version}.tar.gz
URL: http://foo.company.com/

BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root

%description
%{summary}

%prep
%setup -q

%build
# Empty section.

%install
rm -rf %{buildroot}
mkdir -p  %{buildroot}

# in builddir
cp -a * %{buildroot}


%clean
rm -rf %{buildroot}


%files
%defattr(-,root,root,-)
%config(noreplace) %{_sysconfdir}/%{name}/%{name}.conf
%{_bindir}/*

%changelog
* Thu Apr 24 2009  Elia Pinto <devzero2000@rpm5.org> 1.0-1
- First Build

EOF

Build

rpmbuild -ba SPECS/foo.spec

Directory Structure

Simple example of what the directory structure looks like and means in practice...

.
├── BUILD
├── RPMS
├── SOURCES
│   ├── http://cache.ruby-lang.org/pub/ruby/ruby-2.1.2.tar.gz
├── SPECS
│   ├── ruby.spec
├── SRPMS

The BUILD folder is where all files go which are created during a build of the package when you create the rpm.

If the package builds correctly then any rpm(s) created will go into the RPMS and SRPMS folders.

The SRPMS directory only contains source rpms.

Spec files are basically instructions on how the rpm is built and they go in the SPECS folder.

The source tar file should go into the SOURCES directory along with any patches.

Miscellaneous Information

  • Spec file naming convention: <package_name>-<version_number>-<release_number>.spec
  • Spec files reference SOURCEs as Source0, Source1
  • Spec file uses %{variable_name} to dereference values
    • Variables are case insensitive; e.g. %{foo} matches Foo variable
@Integralist
Copy link
Author

Here are some BBC Cosmos notes on the subject...


RPM .spec files provide a lot of power and flexibility but much of this is unnecessary when you're just trying to deploy code to a cloud instance. The documentation below covers a small subset of the features you're likely to need to achieve this.

If you're already comfortable writing .spec files some of the advice may seem strange. If you want to write good, idiomatic .spec files for submission to something like the Fedora project or EPEL then this is the wrong guide; otherwise, read on.

Basic .spec template

Name: package-name-here
Version: version-number-here
Release: 1%{?dist}
Group: System Environment/Daemons
License: Internal BBC use only
Summary: %{name}
Source0: src.tar.gz
Requires: ...
BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
%description
%{name}
%prep
%setup -q -n src/
%build
%install
rm -rf %{buildroot}
%files

Name

The name of your component. For simplicity it should start and end with a letter or number, and only contain letters, numbers, and dashes.

Version

You can use a plain version number such as 1.2.3, or you can use RPM macros to partially or completely define the version at build time. The following examples assume you have defined a buildnum macro with a unique, increasing number, eg by passing something like --define "%buildnum ..." to the build command.

Manually defined version, with an optional build number

Version: 1.2.3%{?buildnum:.%{buildnum}}

Here the version is manually specified (1.2.3) and when an optional buildnum macro is defined it will be added to the end following a . character, eg 1.2.3.99.

Automatically defined version, with a manual fallback

Version: %{?buildnum}%{!?buildnum:0.0.0}

Here the version will be the value of the buildnum macro when it is defined, or 0.0.0 when it isn't – perhaps because the build is being tested locally.

Requires

Use Requires: ... lines to list the packages that need to be installed alongside yours.

You can specify multiple requirements on a single line using comma separators, but using a new line for each requirement will produce clearer diffs when it comes to reviewing changes.

In most cases you can just list the names of the

packages, eg:
Requires: cosmos-ca-tools
Requires: nginx
Requires: nodejs

But if you have specific version requirements that may not be met if the wrong repositories have been configured you can add some constraints, eg:

Requires: nodejs >= 0.10.0

The only supported operators are >, >=, <=, <, and =, and there's no special handling for a requirement like 0.10.X. If needed, this can be simulated with multiple requirements on the same package:

Requires: nodejs >= 0.10.0
Requires: nodejs < 0.11.0

Source0

You will typically pass in the files to be packaged via a .tar.gz file. The actual filename is irrelevant, but it must exist under the SOURCES/ directory, and everything in the archive should be under one top-level directory. For example, do this:

tar -czf SOURCES/src.tar.gz src/

Rather than this:

tar -czf SOURCES/src.tar.gz src/*

In the first case everything in the archive will be under a src/ directory, and in the second there may be multiple files and directories at the top-level.

%prep

The %prep section unpacks the file named in Source0 using the special %setup macro. The -n argument provides the name of the top-level directory, so adjust src/ as appropriate.

The %build and %install sections will start off inside that top-level directory.

%install

The %install section should be a series of shell commands to populate the temporary %{buildroot} directory. The goal here is install your unpacked code under %{buildroot} as if it were the actual root of the filesystem on the target machine.

For example, imagine you have a configuration file that should end up under /etc/myapp/ and a script that should end up under /usr/bin/. Assuming that both are next to each other in your unpacked source, you might do the following:

rm -rf %{buildroot}
mkdir -p %{buildroot}/etc/myapp
mkdir -p %{buildroot}/usr/bin
cp global.conf %{buildroot}/etc/myapp/
cp myscript %{buildroot}/usr/bin/

Note that you should always start by wiping out %{buildroot} to ensure you're not accidentally layering your files on top of some existing state.

An alternative approach would be to prepare the filesystem structure ahead of time in your source .tar.gz file, and then simply copy it all into place in one go, eg:

rm -rf %{buildroot}
cp -r * %{buildroot}/

%files

The %files section is a list of paths under %{buildroot} (which you populated in the %install step) that your package should own, plus information on the user, group, and permissions. You can use simple * wildcards to match multiple paths at a time.

For example, if you've created %{buildroot}/usr/bin/myscript you'll want your package to own the myscript file, but not the parent /usr or /usr/bin directories. And if you've created %{buildroot}/etc/myapp/global.conf you'll want your package to own /etc/myapp and everything underneath, but not the parent /etc directory.
This might be written as follows:

/etc/myapp
/usr/bin/*

The * wildcard isn't necessary here – you could just write /usr/bin/myscript – but it can save a lot of typing if you have multiple files in a directory, or you keep renaming things. Note that wildcards are only expanded once, when the RPM is built, so this example is not claiming ownership of everything under /usr/bin/, just what it finds under %{buildroot}/usr/bin/ at the time.

The above only lists the paths owned by the package, without saying anything about the intended user, group, or permissions. These can be controlled with the %defattr(...) macro, which takes four arguments specifying the file permission, owning user, owning group, and directory permission for any path listed afterwards until the next %defattr(...). Extending the previous example:

%defattr(0644, root, root, 0755)
/etc/myapp
%defattr(0755, root, root, 0755)
/usr/bin/*

The first %defattr says that anything following should be owned by the root user and root group, and have permissions of 0644 if a file or 0755 if a directory. So the /etc/myapp directory will be 0755 root:root, and the /etc/myapp/global.conf will be 0644 root:root.

The second %defattr says that anything following should be owned by the root user and root group, and have permissions of 0755 if a file and also if a directory. So the /usr/bin/myscript file will be 0755 root:root.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment