Skip to main content
Redhat Developers  Logo
  • Products

    Featured

    • Red Hat Enterprise Linux
      Red Hat Enterprise Linux Icon
    • Red Hat OpenShift AI
      Red Hat OpenShift AI
    • Red Hat Enterprise Linux AI
      Linux icon inside of a brain
    • Image mode for Red Hat Enterprise Linux
      RHEL image mode
    • Red Hat OpenShift
      Openshift icon
    • Red Hat Ansible Automation Platform
      Ansible icon
    • Red Hat Developer Hub
      Developer Hub
    • View All Red Hat Products
    • Linux

      • Red Hat Enterprise Linux
      • Image mode for Red Hat Enterprise Linux
      • Red Hat Universal Base Images (UBI)
    • Java runtimes & frameworks

      • JBoss Enterprise Application Platform
      • Red Hat build of OpenJDK
    • Kubernetes

      • Red Hat OpenShift
      • Microsoft Azure Red Hat OpenShift
      • Red Hat OpenShift Virtualization
      • Red Hat OpenShift Lightspeed
    • Integration & App Connectivity

      • Red Hat Build of Apache Camel
      • Red Hat Service Interconnect
      • Red Hat Connectivity Link
    • AI/ML

      • Red Hat OpenShift AI
      • Red Hat Enterprise Linux AI
    • Automation

      • Red Hat Ansible Automation Platform
      • Red Hat Ansible Lightspeed
    • Developer tools

      • Red Hat Trusted Software Supply Chain
      • Podman Desktop
      • Red Hat OpenShift Dev Spaces
    • Developer Sandbox

      Developer Sandbox
      Try Red Hat products and technologies without setup or configuration fees for 30 days with this shared Openshift and Kubernetes cluster.
    • Try at no cost
  • Technologies

    Featured

    • AI/ML
      AI/ML Icon
    • Linux
      Linux Icon
    • Kubernetes
      Cloud icon
    • Automation
      Automation Icon showing arrows moving in a circle around a gear
    • View All Technologies
    • Programming Languages & Frameworks

      • Java
      • Python
      • JavaScript
    • System Design & Architecture

      • Red Hat architecture and design patterns
      • Microservices
      • Event-Driven Architecture
      • Databases
    • Developer Productivity

      • Developer productivity
      • Developer Tools
      • GitOps
    • Secure Development & Architectures

      • Security
      • Secure coding
    • Platform Engineering

      • DevOps
      • DevSecOps
      • Ansible automation for applications and services
    • Automated Data Processing

      • AI/ML
      • Data Science
      • Apache Kafka on Kubernetes
      • View All Technologies
    • Start exploring in the Developer Sandbox for free

      sandbox graphic
      Try Red Hat's products and technologies without setup or configuration.
    • Try at no cost
  • Learn

    Featured

    • Kubernetes & Cloud Native
      Openshift icon
    • Linux
      Rhel icon
    • Automation
      Ansible cloud icon
    • Java
      Java icon
    • AI/ML
      AI/ML Icon
    • View All Learning Resources

    E-Books

    • GitOps Cookbook
    • Podman in Action
    • Kubernetes Operators
    • The Path to GitOps
    • View All E-books

    Cheat Sheets

    • Linux Commands
    • Bash Commands
    • Git
    • systemd Commands
    • View All Cheat Sheets

    Documentation

    • API Catalog
    • Product Documentation
    • Legacy Documentation
    • Red Hat Learning

      Learning image
      Boost your technical skills to expert-level with the help of interactive lessons offered by various Red Hat Learning programs.
    • Explore Red Hat Learning
  • Developer Sandbox

    Developer Sandbox

    • Access Red Hat’s products and technologies without setup or configuration, and start developing quicker than ever before with our new, no-cost sandbox environments.
    • Explore Developer Sandbox

    Featured Developer Sandbox activities

    • Get started with your Developer Sandbox
    • OpenShift virtualization and application modernization using the Developer Sandbox
    • Explore all Developer Sandbox activities

    Ready to start developing apps?

    • Try at no cost
  • Blog
  • Events
  • Videos

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

Share:

    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

    • More Essential AI tutorials for Node.js Developers

    • How to run a fraud detection AI model on RHEL CVMs

    • How we use software provenance at Red Hat

    • Alternatives to creating bootc images from scratch

    • How to update OpenStack Services on OpenShift

    Red Hat Developers logo LinkedIn YouTube Twitter Facebook

    Products

    • Red Hat Enterprise Linux
    • Red Hat OpenShift
    • Red Hat Ansible Automation Platform

    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

    Red Hat legal and privacy links

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

    Report a website issue