Skip to main content
Redhat Developers  Logo
  • AI

    Get started with AI

    • Red Hat AI
      Accelerate the development and deployment of enterprise AI solutions.
    • AI learning hub
      Explore learning materials and tools, organized by task.
    • AI interactive demos
      Click through scenarios with Red Hat AI, including training LLMs and more.
    • AI/ML learning paths
      Expand your OpenShift AI knowledge using these learning resources.
    • AI quickstarts
      Focused AI use cases designed for fast deployment on Red Hat AI platforms.
    • No-cost AI training
      Foundational Red Hat AI training.

    Featured resources

    • OpenShift AI learning
    • Open source AI for developers
    • AI product application development
    • Open source-powered AI/ML for hybrid cloud
    • AI and Node.js cheat sheet

    Red Hat AI Factory with NVIDIA

    • Red Hat AI Factory with NVIDIA is a co-engineered, enterprise-grade AI solution for building, deploying, and managing AI at scale across hybrid cloud environments.
    • Explore the solution
  • Learn

    Self-guided

    • Documentation
      Find answers, get step-by-step guidance, and learn how to use Red Hat products.
    • Learning paths
      Explore curated walkthroughs for common development tasks.
    • See all learning

    Hands-on

    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.
    • Interactive labs
      Learn by doing in these hands-on, browser-based experiences.
    • Interactive demos
      Click through product features in these guided tours.

    Browse by topic

    • AI/ML
    • Automation
    • Java
    • Kubernetes
    • Linux
    • See all topics

    Training & certifications

    • Courses and exams
    • Certifications
    • Skills assessments
    • Red Hat Academy
    • Learning subscription
    • Explore training
  • Build

    Get started

    • Red Hat build of Podman Desktop
      A downloadable, local development hub to experiment with our products and builds.
    • Developer Sandbox
      Spin up Red Hat's products and technologies without setup or configuration.

    Download products

    • Access product downloads to start building and testing right away.
    • Red Hat Enterprise Linux
    • Red Hat AI
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Featured

    • Red Hat build of OpenJDK
    • Red Hat JBoss Enterprise Application Platform
    • Red Hat OpenShift Dev Spaces
    • Red Hat Developer Toolset

    References

    • E-books
    • Documentation
    • Cheat sheets
    • Architecture center
  • Community

    Get involved

    • Events
    • Live AI events
    • Red Hat Summit
    • Red Hat Accelerators
    • Community discussions

    Follow along

    • Articles & blogs
    • Developer newsletter
    • Videos
    • Github

    Get help

    • Customer service
    • Customer support
    • Regional contacts
    • Find a partner

    Join the Red Hat Developer program

    • Download Red Hat products and project builds, access support documentation, learning content, and more.
    • Explore the benefits

Write toolchain-agnostic RPM spec files for GCC and Clang

July 28, 2021
Timm Baeder
Related topics:
C, C#, C++LinuxOpen source
Related products:
Red Hat Enterprise Linux

    With the addition of the %toolchain macro to the redhat-rpm-config package, packages can easily switch between the GNU Compiler Collection (GCC) and the Clang compiler. This package change is not yet supported by Fedora, and package maintainers need good reasons to switch from the GCC default to Clang. Maintainers also need to watch out for a few nuances to make (and keep) a package specification file buildable with both toolchains.

    This article looks at the necessary changes and best practices to allow a spec file to build with both GCC and Clang in a variety of cases.

    Preparing a spec file

    In the most basic case, nothing needs to be done. The specification file will automatically pick up whatever compiler the buildroot defines as the default. You can check the default by looking at the value of the %toolchain macro in the %build section:

    %build
    echo "Toolchain is %toolchain"

    Similarly, rpm --eval "%toolchain" will print the value of the %toolchain macro. Setting the macro to either "gcc" or "clang" will select the toolchain for the given spec file. Setting the macro explicitly is useful if the package needs to be built with a specific toolchain:

    %global toolchain clang

    For testing, it might make sense to add a conditional variable to the spec file, so it can be easily built with either toolchain:

    %bcond_with toolchain_clang
    
    %if %{with toolchain_clang}
    %global toolchain clang
    %else
    %global toolchain gcc
    %endif

    Adding the conditional variable should suffice for the minimal setup. In some cases, you might need to make additional changes such as adjusting the BuildRequires. This is because the %toolchain macro does not automatically add the appropriate toolchain BuildRequires. With this setup, building a simple RPM via rpmbuild --with=toolchain_clang will select the Clang toolchain while not passing anything or passing --with=gcc will select GCC.

    The value of the %toolchain macro will later determine the value of the various environment variables, as well as what build flags will be passed to the build system. So this macro should be set as early as possible in the file.

    Build systems

    Most C and C++ software projects use a popular build system such as Autotools, CMake, or Meson. These build systems usually try to follow standard practices such as respecting common environment variables, e.g., CC, CXX, or CFLAGS. They are readily supported by RPM via their respective macros, such as %cmake for CMake, %configure for Autotools, and %meson for Meson.

    After setting those macros, use %make_build to actually compile the project. This macro will run make and pass a few flags for parallel builds. There are special macros to let CMake and Meson build the application, in case the build uses Ninja instead of make: %cmake_build and %meson_build.

    Just using these macros works for the simplest projects. Some projects, however, do not use any of the common build systems and require special care.

    Hand-written makefiles

    Hand-written makefiles are still relatively common, especially in smaller software projects. Unfortunately, makefiles do not usually respect the environment variables mentioned earlier. This makes it impossible for distributions to inject their hardening C flags or change the compiler in use.

    When using hand-written makefiles in a Fedora RPM spec file, the %make_build macro works as well. However, that does not define the CFLAGS environment variable and others. %set_build_flags can be used to set those flags in the makefile:

    %build
    %set_build_flags
    %make_build

    %set_build_flags also sets the CC environment variable, so if the compiler needs to be invoked explicitly, $CC should be used instead of hard-coding gcc or clang, or even /usr/bin/cc.

    If the provided makefile respects the standard environment variables, you are all set with this approach. If it doesn't, it usually needs to be patched, or the spec file needs to use another environment variable, for example EXTRA_CFLAGS:

    %build
    %set_build_flags
    %make_build EXTRA_CFLAGS="$CFLAGS"

    Custom build setups

    This is more of a worst-case scenario for packagers, but many projects still have custom build setups (for example, a shell script that compiles the source code) or no build setup at all.

    If the project does not have any build setup, it's easiest to simply use %set_build_flags again and compile using $CC:

    %build
    %set_build_flags
    
    # Compile main library
    $CC lib.c -c -o lib.o $CFLAGS $LDFLAGS

    However, if the project uses a custom shell script (or something similar), you're out of luck again. Using %set_build_flags is still a good idea, but you have to inspect the build script to find out what environment variables have to be set or what command-line parameters have to be passed.

    Toolchain-specific changes

    One common problem that spec files run into is that they want to add (or override) flags from the CFLAGS variable to keep a working build. That is of course easy to do:

    %build
    %set_build_flags
    CFLAGS="$CFLAGS -fno-some-flag"

    In this case, we assume that a new version of the compiler in use has introduced a new flag that's enabled by default, -fsome-flag. This flag causes problems when the project in question is compiled, however, so the package maintainer has decided to disable it again by passing -fno-some-flag. When trying to build this package with a different compiler, it can happen that the build fails because the compiler does not know about -fsome-flag or -fno-some-flag. When -fno-some-flag needs to be passed only for a specific compiler, check the %toolchain macro for the compiler that requires the flag:

    %build
    %set_build_flags
    %if "%toolchain" == "gcc"
      CFLAGS="$CFLAGS -fno-some-flag"
    %endif

    As implemented in redhat-rpm-macros, the %toolchain macro always evaluates to either "gcc" or "clang." Note that one cannot generally check $CC the same way, because that variable might point to an obscure binary in cross-compilation scenarios.

    Link-time optimization with Clang

    Link-time optimization (LTO) is enabled by default on Fedora, which means that the $CFLAGS variable (set by %set_build_flags) contains a variation of the -flto flag. The flags used by link-time optimization are saved in the %_lto_cflags variable. When porting a package to compile with the Clang toolchain, it is important to know that the link-time optimization flags have to be added to the $LDFLAGS variable as well. %set_build_flags will take care of that.

    When the package needs some non-standard variable, the flags from %_lto_cflags need to be added manually. In the worst case, link-time optimization has to be disabled entirely. This change is often necessary when the project has some home-grown LTO setup that expects GCC. Disabling link-time optimization can be done by setting %_lto_cflags to %{nil}:

    %global _lto_cflags %{nil}

    Conclusion

    Supporting both the GCC and Clang toolchains is easy if the package uses a standard build system and respects standard environment variables. There are still tons of special cases, which can often be handled by checking the %toolchain macro value and handling the problem depending on the toolchain in use.

    Note that currently (as of Fedora 34) all packages in Fedora are built with GCC, but there is a proposal for changing this. See Fedora's Changes/CompilerPolicy page for details.

    Good documentation for these matters is scarce, but I like to look at the macros defined in the RPM repository as well as the redhat-rpm-config repository. Looking at the output of rpm --showrc can also provide insight.

    Last updated: October 6, 2022

    Related Posts

    • Use multiple compilers to build better projects

    • Customize the compilation process with Clang: Optimization options

    • Profile-guided optimization in Clang: Dealing with modified sources

    • Optimizing the Clang compiler’s line-to-offset mapping

    • RPM packaging: A simplified guide to creating your first RPM

    Recent Posts

    • Federated identity across the hybrid cloud using zero trust workload identity manager

    • Confidential virtual machine storage attack scenarios

    • Introducing virtualization platform autopilot

    • Integrate zero trust workload identity manager with Red Hat OpenShift GitOps

    • Best Practice Configuration and Tuning for Linux and Windows VMs

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Platforms

    • Red Hat AI
    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform
    • See all products

    Build

    • Developer Sandbox
    • Developer tools
    • Interactive tutorials
    • API catalog

    Quicklinks

    • Learning resources
    • E-books
    • Cheat sheets
    • Blog
    • Events
    • Newsletter

    Communicate

    • About us
    • Contact sales
    • Find a partner
    • Report a website issue
    • Site status dashboard
    • Report a security problem

    RED HAT DEVELOPER

    Build here. Go anywhere.

    We serve the builders. The problem solvers who create careers with code.

    Join us if you’re a developer, software engineer, web designer, front-end designer, UX designer, computer scientist, architect, tester, product manager, project manager or team lead.

    Sign me up

    Red Hat legal and privacy links

    • About Red Hat
    • Jobs
    • Events
    • Locations
    • Contact Red Hat
    • Red Hat Blog
    • Inclusion at Red Hat
    • Cool Stuff Store
    • Red Hat Summit
    © 2026 Red Hat

    Red Hat legal and privacy links

    • Privacy statement
    • Terms of use
    • All policies and guidelines
    • Digital accessibility

    Chat Support

    Please log in with your Red Hat account to access chat support.