How do I read this magical “spec-file”?

Let’s start with a minimal example spec-file. Here you can see a few examples depending on the build-system/tools used during the packaging.

%global debug_package %{nil}
%global         commit    836dc6a0efd9849cb194e88e4aa2387436bb079b

Name:           span
Version:        0~20250521git836dc6a
Release:        %autorelease
Summary:        Implementation of C++20's std::span for older compilers

License:        BSL-1.0

URL:            https://github.com/tcbrindle/span
Source:         %{url}/archive/%{commit}.tar.gz

# Fix build without exceptions
Patch0:         https://github.com/tcbrindle/span/pull/53.patch
# Make the project installable
Patch1:         span-Fedora_patches.patch

BuildRequires:  cmake
BuildRequires:  gcc-c++
BuildRequires:  cmake(Catch2)

%global _description %{expand:
Single-header implementation of C++20's std::span, conforming to the C++20
committee draft. It is compatible with C++11, but will use newer language
features if they are available.

It differs from the implementation in the Microsoft GSL in that it is
single-header and does not depend on any other GSL facilities. It also works
with C++11, while the GSL version requires C++14.}

%description %{_description}

%package devel
Summary:        Development files for span
# Header-only package
Provides:       span-static = %{version}-%{release}

%description devel %{_description}

This package contains the development files.


%prep
%autosetup -p1 -n span-%{commit}


%conf
%cmake


%build
%cmake_build


%install
%cmake_install


%check
%ctest


%files devel
%dir %{_includedir}/tcb
%{_includedir}/tcb/span.hpp
%license LICENSE_1_0.txt
%doc README.md


%changelog
%autochangelog
Name:           python-pytest-check
Version:        2.5.4
Release:        %autorelease
Summary:        A pytest plugin that allows multiple failures per test

License:        MIT
URL:            https://github.com/okken/pytest-check
Source:         %{pypi_source pytest_check}

BuildArch:      noarch
BuildRequires:  python3-devel

%global _description %{expand:
A pytest plugin that allows multiple failures per test.}

%description %_description

%package -n python3-pytest-check
Summary:        %{summary}
%description -n python3-pytest-check %_description


%prep
%autosetup -n pytest_check-%{version}


%generate_buildrequires
%pyproject_buildrequires


%build
%pyproject_wheel


%install
%pyproject_install
%pyproject_save_files pytest_check


%check
%pytest


%files -n python3-pytest-check -f %{pyproject_files}
%license LICENSE.txt
%doc README.md


%changelog
%autochangelog

Step1: What is being done?

The main parts of the spec file are the build process steps which are separated into various sections:

%prep

Prepare (extract) the sources and

%generate_buildrequires

Add additional BuildRequires dynamically (more on that later)

%conf

Run configure or equivalents

%build

Do the main build steps of the project

%install

Install all files in a staging location as they will be seen after you dnf install the package

%check

Run available tests preferably against the staged files

The sections are executed in that order, and can be omitted.

Let’s look at the minimal example and decompose what’s happening:

%prep
%autosetup -p1 -n span-%{commit}


%conf
%cmake


%build
%cmake_build


%install
%cmake_install


%check
%ctest
%prep

Extract the source defined in Source, apply any patches defined by Patch, and cd into the folder inside the archive defined by -n option.

(Common across all build systems)

%conf

Run a cmake (configure) step with any relevant -D options

%build

Run the cmake --build step

%install

Run cmake --install

%check

Run the provided ctest (in the build directory)

%prep
%autosetup -n pytest_check-%{version}


%generate_buildrequires
%pyproject_buildrequires


%build
%pyproject_wheel


%install
%pyproject_install
%pyproject_save_files pytest_check


%check
%pytest
%prep

Extract the source defined in Source, apply any patches defined by Patch, and cd into the folder inside the archive defined by -n option.

(Common across all build systems)

%pyproject_buildrequires

Get all build-system.requires, project.requires from pyproject.toml and add them to the current BuildRequires

%build

Run pip wheel

%install

Install the built wheel

Save the installed file list under %{pyproject_files}

%check

Run pytest

Step0: Wait, where are the sources coming from?

Ok, I’ve lied, the most important thing you should be interested in as a packager is the metadata sections which, among many other things, defines what are the sources (and patches) currently used. Otherwise, there is little variance between the actual build instructions of each package.

Name:           span
Version:        0~20250521git836dc6a
Release:        %autorelease
Summary:        Implementation of C++20's std::span for older compilers

License:        BSL-1.0

URL:            https://github.com/tcbrindle/span
Source:         %{url}/archive/%{commit}.tar.gz

# Fix build without exceptions
Patch0:         https://github.com/tcbrindle/span/pull/53.patch
# Make the project installable
Patch1:         span-Fedora_patches.patch

BuildRequires:  cmake
BuildRequires:  gcc-c++
BuildRequires:  cmake(Catch2)
Name:           python-pytest-check
Version:        2.5.4
Release:        %autorelease
Summary:        A pytest plugin that allows multiple failures per test

License:        MIT
URL:            https://github.com/okken/pytest-check
Source:         %{pypi_source pytest_check}

BuildArch:      noarch
BuildRequires:  python3-devel

These Source and Patch lines can point to an external url that would be downloaded or a local file present next to the spec file.

Tip

Use spectool -g *.spec to download all of the sources defined in the spec file

Step2: Dependencies

Of course the sources and instructions are not sufficient to build the package, you also need the build-time and runtime dependencies of the current package.

Name:           span
Version:        0~20250521git836dc6a
Release:        %autorelease
Summary:        Implementation of C++20's std::span for older compilers

License:        BSL-1.0

URL:            https://github.com/tcbrindle/span
Source:         %{url}/archive/%{commit}.tar.gz

# Fix build without exceptions
Patch0:         https://github.com/tcbrindle/span/pull/53.patch
# Make the project installable
Patch1:         span-Fedora_patches.patch

BuildRequires:  cmake
BuildRequires:  gcc-c++
BuildRequires:  cmake(Catch2)
Name:           python-pytest-check
Version:        2.5.4
Release:        %autorelease
Summary:        A pytest plugin that allows multiple failures per test

License:        MIT
URL:            https://github.com/okken/pytest-check
Source:         %{pypi_source pytest_check}

BuildArch:      noarch
BuildRequires:  python3-devel

These are defined with the BuildRequires and Requires fields. Of course managing these fields manually would be very time-consuming, so often these are kept to the bare minimum needed. The real dependencies are often defined dynamically either from the source files for the BuildRequires using %generate_buildrequires or from the installed files for the Requires which are handled behind the scenes (e.g. the dependencies on the libraries is never defined manually instead these are extracted from the binary/library files).

Step3: %files and %package sections

After you’ve installed the files, you also need to define the expected files and their locations to be packaged, which is defined in the %files section.

However, often you would want to separate where the files are located, e.g. the development files being in a *-devel sub-package, the documentations in a *-docs sub-package, etc. This is done by defining various %package sections sharing the same metadata fields as the top level/source package.

%package devel
Summary:        Development files for span
# Header-only package
Provides:       span-static = %{version}-%{release}

%description devel %{_description}

This package contains the development files.


%files devel
%dir %{_includedir}/tcb
%{_includedir}/tcb/span.hpp
%license LICENSE_1_0.txt
%doc README.md
%package -n python3-pytest-check
Summary:        %{summary}
%description -n python3-pytest-check %_description


%files -n python3-pytest-check -f %{pyproject_files}
%license LICENSE.txt
%doc README.md

Step4: Macros

As you’ve already seen throughout the files, the spec file is riddled with macros which you can find as either %{macro_name} or %macro_name. These simplify the repetition of various parts of the spec file. Some of these are pre-defined from corresponding fields such as %version or %url, defined in some commonly sourced /usr/lib/rpm/macros.d/macros.* file such as for the %cmake, %pyproject_wheel etc., or they can be defined/overriden directly in the spec file with %global or equivalents.

%global debug_package %{nil}
%global         commit    836dc6a0efd9849cb194e88e4aa2387436bb079b

Name:           span
Version:        0~20250521git836dc6a
Release:        %autorelease
Summary:        Implementation of C++20's std::span for older compilers

License:        BSL-1.0

URL:            https://github.com/tcbrindle/span
Source:         %{url}/archive/%{commit}.tar.gz

# Fix build without exceptions
Patch0:         https://github.com/tcbrindle/span/pull/53.patch
# Make the project installable
Patch1:         span-Fedora_patches.patch

BuildRequires:  cmake
BuildRequires:  gcc-c++
BuildRequires:  cmake(Catch2)

%global _description %{expand:
Single-header implementation of C++20's std::span, conforming to the C++20
committee draft. It is compatible with C++11, but will use newer language
features if they are available.

It differs from the implementation in the Microsoft GSL in that it is
single-header and does not depend on any other GSL facilities. It also works
with C++11, while the GSL version requires C++14.}

%description %{_description}
Name:           python-pytest-check
Version:        2.5.4
Release:        %autorelease
Summary:        A pytest plugin that allows multiple failures per test

License:        MIT
URL:            https://github.com/okken/pytest-check
Source:         %{pypi_source pytest_check}

BuildArch:      noarch
BuildRequires:  python3-devel

%global _description %{expand:
A pytest plugin that allows multiple failures per test.}

%description %_description

Step5: Control parameters

Sometimes you will find a spec file defining %bcond fields which are basically just control fields to turn on/off a process during the build with the %if and %with_*/%without_* macros. More on how to actually invoke them in the how to build section of this guide.

Step6: The rest

As you can see not all fields are documented here as this is beyond the scope of this guide. Some of the fields usage is straightforward. Other times the fields usage have quite a bit of nuance attached to it which is best explored with a fellow Fedora packager, most of which being in the Fedora #devel room.