C and C++ Packaging Guidelines
Introduction
The C and C++ languages and runtimes are one of the most common development frameworks for packages in fedora. As such there is a wide variety of quality, style, and convention in all of those packages. The follow document provides best practice for certain aspects of C and C++ packaging.
Packaging
BuildRequires and Requires
If your application is a C or C++ application
you must list a BuildRequires against gcc, gcc-c++ or clang.
Those packages will include everything that is required
to build a standards conforming C or C++ application.
If your library includes standard C or C++ headers,
you must list BuildRequires against gcc, gcc-c++, or clang
to install the needed standards conforming headers.
If at runtime you use cpp to process C or C++ language headers
then you have no choice but to use Requires for gcc, gcc-c++, or clang
to install the required headers
for a standard conforming C or C++ application.
In the future this might change
if a set of standard C or C++ language headers are provided
by a special-purpose provides e.g. c-headers or c++-headers.
You need not include a BuildRequires or Requires on glibc-headers,
or any other core C or C++ implementation package
unless you have a specific and special need
e.g. static compilation requires the .*-static library packages
e.g. BuildRequires: glibc-static.
The default use case of a dynamically compiled C or C++ application
is taken care of by the gcc, gcc-c++, and clang packages.
Please refer to Compiler Guidelines for the list of supported compilers for C and C++ compilers.
Packaging Q&A
-
Do I need a
Requires: glibcto ensure I have the C runtime installed for my application?No. RPM will automatically determine what ELF libraries you need based on the binaries in your package. This is sufficient to cause glibc to be installed.
-
Do I need to include a
Requires: libgcc?If you are using an API from
libgccdirectly, then yes, you must have aRequires: libgcc. In general thoughglibcrequireslibgcc, so it is always installed.
Libraries
Libraries should have unique shared object names
(SONAMEs via -Wl,-soname=libfoo.so)
that do not conflict with other library SONAMEs used in the distribution.
For example there should be only one libfoo.so in the distribution.
The exception is when there are multiple implementations of the same library
libfoo.so provided by different authors and each conflicts with the other.
In this case both libfoo.so must provide exactly the same interface,
but with a different implementation.
Having two libfoo.so each with a different API is bad practice
and makes it harder to package and distribute those packages.
Versioned Symbols
Without versioned symbols, RPM will generate a dependency expression naming the library but without a version, effectively setting "$Major.0.0" as the minimum version. Versioned symbols provide the information required to ensure that libraries are actually new enough to run the software that links to them.
Examine the capabilities provided by the binary rpm:
rpm -qp --provides <package>. A package with shared libraries will
list the library as libc.so.6()(64bit) and if the library provides
versioned symbols it will also list the library with versions as
libm.so.6(GLIBC_2.41)(64bit).
Package maintainers are encouraged to work with upstream projects to add versioned symbols to libraries that do not include them yet.
Adding symbol versions is simple for the majority of libraries, initially requiring only a symbol map and one additional argument to the linker during the build process.
generate_initial_map.sh:
#!/bin/sh
echo "# Avoid modifying a symbol set after it has been released"
echo "# When adding features in a new release, add a new set"
echo "# Removing features is a breaking change"
echo "$2 {"
echo " global:"
objdump -T $1 | \
grep -F .text | \
awk '{print $7;}' | \
c++filt | \
awk '/[() ]/ {print " \"" $0 "\";";} \
!/[() ]/ {print " " $0 ";";}' | \
sort
echo "}"
Run generate_initial_map.sh /path/to/library.so.1 <LIBRARY>_<VERSION> to
generate a map file.
Adding version-script to Automake
The GNU Portability Library manual includes examples of using version-script in automake. In Makefile.am:
if HAVE_LD_VERSION_SCRIPT
libfoo_la_LDFLAGS += -Wl,--version-script=$(srcdir)/libfoo.map
endif
Adding version-script to CMake
The BSD-3-Clause licensed protobuf project includes examples of using version-script in CMake.
In CMakeLists.txt, check the linker for support:
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map
"{
global:
main;
local:
*;
};")
# CheckLinkerFlag module available in CMake >=3.18.
if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.18)
include(CheckLinkerFlag)
check_linker_flag(CXX -Wl,--version-script=${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map project_HAVE_LD_VERSION_SCRIPT)
endif()
file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/cmaketest.map)
And, where the library is defined:
if(project_HAVE_LD_VERSION_SCRIPT)
target_link_options(libfoo PRIVATE -Wl,--version-script=${protobuf_source_dir}/src/libfoo.map)
set_target_properties(libfoo PROPERTIES
LINK_DEPENDS ${project_source_dir}/src/libfoo.map)
endif()
Adding version-script to Meson
Meson’s test cases include examples of using version-script.
# Solaris 11.4 ld supports --version-script only when you also specify
# -z gnu-version-script-compat
if meson.get_compiler('c').get_linker_id() == 'ld.solaris'
add_project_link_arguments('-Wl,-z,gnu-version-script-compat', language: 'C')
endif
# Static map file
mapfile = 'bob.map'
vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile)
l = shared_library('bob', 'bob.c', link_args : vflag, link_depends : mapfile)
Want to help? Learn how to contribute to Fedora Docs ›