Let’s see how the same C++ project (or at least very similar ones) would look like in different modern build systems. Courtesy of ChatGPT.


I requested that each project contains the following. This covers many real-life uses.

  • A static library dependency.
  • Custom include path.
  • Custom target / output path.
  • Custom compiler flags for Windows.

The list is in no particular order.

Build generators

First, we’ll look into the build systems that are actually build generators - they create project files or build scripts for various other IDEs and build executors, but can’t build (invoke the compiler and linker) themselves.

Meson

project('my_project', 'cpp')

# Define source files
src_files = ['src/file1.cpp', 'src/file2.cpp']
inc_dirs = include_directories('include')

# Define the library target as a static library
my_library = static_library('my_library', 
    sources: src_files,
    include_directories: inc_dirs,
    type: 'static',
    install: true,
)

# Add dependency on another static library
my_dependency = static_library('my_dependency',
    sources: [],
    link_with: 'path/to/my/lib.a',
)

# Set compiler flags for MSVC on Windows
if (is_windows())
    add_project_arguments('/EHsc', '/std:c++latest', language: 'cpp', when: 'cpp')
endif()

# Define the executable target and link against the library
my_executable = executable('my_executable',
    sources: ['src/main.cpp'],
    link_with: my_library,
)

CMake

cmake_minimum_required(VERSION 3.10)

project(my_project LANGUAGES CXX)

# Set the target name and source files for the library
add_library(my_library STATIC 
    src/file1.cpp 
    src/file2.cpp)

# Add a dependency on another static library
add_library(my_dependency STATIC IMPORTED)
set_target_properties(my_dependency PROPERTIES IMPORTED_LOCATION path/to/my/lib.a)
target_link_libraries(my_library PRIVATE my_dependency)

# Set compiler flags for MSVC on Windows
if (MSVC)
    target_compile_options(my_library PRIVATE /EHsc /std:c++latest)
endif()

# Set a custom include path
target_include_directories(my_library PUBLIC include)

# Set a custom output path
set_target_properties(my_library PROPERTIES
    ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
)

# Define an executable target and link against the library
add_executable(my_executable src/main.cpp)
target_link_libraries(my_executable PRIVATE my_library)

Premake 5

workspace "my_project"
   configurations { "Debug", "Release" }
   -- Set the target name and source files for the library
   project "my_library"
      kind "StaticLib"
      language "C++"
      targetname "my_library"
      files { "src/file1.cpp", "src/file2.cpp" }

      -- Add a dependency on another static library
      includedirs { "path/to/my/include" }
      links { "my_dependency" }

      -- Set compiler flags for MSVC on Windows
      filter { "system:windows", "action:vs*" }
         flags { "/EHsc", "/std:c++latest" }
      filter {}

      -- Set a custom output path
      targetdir "lib"

   -- Define an executable target and link against the library
   project "my_executable"
      kind "ConsoleApp"
      language "C++"
      files { "src/main.cpp" }
      links { "my_library" }

      -- Set a custom include path
      includedirs { "include" }

qmake

# Set the target name and source files for the library
TARGET = my_library
TEMPLATE = lib
CONFIG += staticlib
SOURCES += src/file1.cpp \
           src/file2.cpp

# Add a dependency on another static library
INCLUDEPATH += path/to/my/include
LIBS += -Lpath/to/my/lib -lmy_dependency

# Set compiler flags for MSVC on Windows
win32: QMAKE_CXXFLAGS += /EHsc /std:c++latest

# Set a custom output path
DESTDIR = lib

# Define an executable target and link against the library
TARGET = my_executable
TEMPLATE = app
SOURCES += src/main.cpp
INCLUDEPATH += include
LIBS += -Llib -lmy_library

WAF

import os

def configure(conf):
    # Set the compiler flags for MSVC on Windows
    if conf.env.CC_NAME == 'msvc':
        conf.env.CXXFLAGS += ['/EHsc', '/std:c++latest']

    # Set the custom include path
    conf.env.INCLUDES += [conf.path.abspath('include')]

def build(bld):
    # Set the target name and source files for the library
    my_library = bld.create_static_library(
        name='my_library',
        src=[
            'src/file1.cpp',
            'src/file2.cpp',
        ],
        includes='include',
    )

    # Add a dependency on another static library
    my_dependency = bld.read_shlib(
        'my_dependency',
        paths=['path/to/my/lib.a'],
        features=['cxx', 'cxxstlib'],
        export_includes=['include'],
    )
    my_library.use_libs(my_dependency)

    # Define an executable target and link against the library
    my_executable = bld.program(
        source='src/main.cpp',
        target='my_executable',
        use=my_library,
    )

    # Set a custom output path
    my_library.post()
    my_library.path = os.path.join(bld.bldnode.abspath(), 'lib')

GN (Generate Ninja)

# Set the target name and source files for the library
my_library = static_library("my_library") {
    sources = [
        "src/file1.cpp",
        "src/file2.cpp",
    ],
    includes = [ "include" ],
}

# Add a dependency on another static library
my_dependency = static_library("my_dependency") {
    deps = [ ":my_lib" ],
    include_dirs = [ "include" ],
}
my_library.deps += [ my_dependency ]

# Set compiler flags for MSVC on Windows
if (target_cpu == "x86_64" && toolchain == "msvc") {
    my_library.cflags_c = [ "/EHsc", "/std:c++latest" ]
}

# Define an executable target and link against the library
executable("my_executable") {
    sources = [ "src/main.cpp" ],
    deps = [ ":my_library" ],
}

# Set a custom output path
output_directory = get_target_gen_dir() + "/lib"
if (is_win) {
    output_directory += "/$configuration"
}
my_library.output_dir = output_directory

Sharpmake

using System;
using System.Collections.Generic;
using Sharpmake;

[module: Sharpmake.Include("CustomOptions.cs")]
[module: Sharpmake.Include("CustomOutput.cs")]

[Generate]
class MyProject : CPlusPlusProject
{
    public MyProject()
    {
        Name = "my_project";

        AddTargets(new Target(
            Platform.win64,
            DevEnv.vs2019,
            Optimization.Debug | Optimization.Release));

        SourceRootPath = @"[project.SharpmakeCsPath]\..\src";

        AddLibrary("my_library", new[] {
            @"src\file1.cpp",
            @"src\file2.cpp",
        });

        AddLinkerOptions("my_library", new[] {
            "-Wl,-rpath,$ORIGIN",
        });

        AddIncludeDirs("my_library", new[] {
            @"include",
        });

        AddLibrary("my_dependency", new[] {
            @"path\to\my\lib.lib",
        });

        AddLinkerOptions("my_dependency", new[] {
            "-Lpath/to/my",
        });

        AddDependency("my_library", "my_dependency");

        AddExecutable("my_executable", new[] {
            @"src\main.cpp",
        }, "my_library");
    }

    [Configure()]
    public void ConfigureAll(Configuration conf, Target target)
    {
        conf.IncludePaths.Add(@"[project.SharpmakeCsPath]\..\include");

        conf.Options.Add(SharpmakeOptions.Vc.Compiler.CppLanguageStandard.CPPLatest);

        if (target.Platform == Platform.win64)
        {
            conf.Options.Add(SharpmakeOptions.Vc.Compiler.Exceptions.EnableWithSEH);
            conf.Options.Add(SharpmakeOptions.Vc.Compiler.RuntimeLibrary.MultiThreadedDebugDLL);
            conf.Options.Add(SharpmakeOptions.Vc.Compiler.RuntimeLibrary.MultiThreadedDLL);
            conf.Options.Add(SharpmakeOptions.Vc.Compiler.WarningLevel.Level3);

            conf.Output = "[conf.Name]\\";
            conf.TargetPath = "[conf.OutputDirectory]\\bin\\[target.Platform]\\[conf.Name]\\";
            conf.IntermediatePath = "[conf.OutputDirectory]\\obj\\[target.Platform]\\[conf.Name]\\";
        }
    }
}

Tundra

Settings {
    Env = {
        CXXOPTS = {
            Release = { "/EHsc", "/std:c++latest" },
            Debug   = { "/EHsc", "/std:c++latest", "-g" },
        },
        VARIANT_DIR = ".build/$(VARIANT)",
        OUTPUT_DIR  = "$(VARIANT_DIR)/bin",
        OBJECT_DIR  = "$(VARIANT_DIR)/obj",
        LIB_DIR     = "$(VARIANT_DIR)/lib",
    },
    Platforms = {
        win64 = {
            CXX = "cl",
            CXXOPTS = {
                Debug   = { "/MDd" },
                Release = { "/MD" },
            },
            LD = "link",
            LDOPTS = {
                Debug   = { "/DEBUG" },
                Release = {},
            },
            LIBRARY_PREFIX = "",
            LIBRARY_SUFFIX = ".lib",
            EXECUTABLE_SUFFIX = ".exe",
        },
    },
}

Program {
    Name = "my_executable",
    Sources = { "src/main.cpp" },
    Libs = { "my_library" },
    Includes = { "include" },
    Frameworks = {},
    Defs = {},
    PreBuildCommands = {},
    PostBuildCommands = {},
    Variant = "Release",
    Config = "win64",
}

Library {
    Name = "my_library",
    Sources = { "src/file1.cpp", "src/file2.cpp" },
    Includes = { "include" },
    Frameworks = {},
    Defs = {},
    Variant = "Release",
    Config = "win64",
}

StaticLibrary {
    Name = "my_dependency",
    Sources = {},
    Libs = { "path/to/my/lib.a" },
    Includes = {},
    Frameworks = {},
    Defs = {},
    Variant = "Release",
    Config = "win64",
}

Executors / generators

These systems can both generate projects for other build systems / IDEs, and build their own projects without invoking an additional lower-level build system - your choice how to use them.

qbs

import qbs

// Set up the project
Project {
    name: "my_project"

    // Set up the library target
    StaticLibrary {
        name: "my_library"
        files: [
            "src/file1.cpp",
            "src/file2.cpp"
        ]

        // Add a dependency on another static library
        cpp.includePaths: ["path/to/my/include"]
        cpp.systemIncludePaths: ["path/to/my/system/include"]
        cpp.libraryPaths: ["path/to/my/lib"]
        cpp.staticLibraries: ["my_dependency"]

        // Set compiler flags for MSVC on Windows
        cpp.compilerFlags: ["-EHsc", "-std:c++latest"]
        qbs.buildVariant: "release"
        qbs.architecture: "x86_64"

        // Set a custom output path
        destinationDirectory: "lib"
    }

    // Set up the executable target
    cpp.executableTemplate: "console"
    cpp.applicationName: "my_executable"
    cpp.sourceFiles: ["src/main.cpp"]
    cpp.includePaths: ["include"]
    cpp.staticLibraries: ["my_library"]

    // Set a custom output path
    files: {
        "my_executable": "bin/my_executable"
    }
}

Bazel

# Define the Bazel workspace
workspace(name = "my_project")

# Define the static library target
cc_library(
    name = "my_library",
    srcs = ["src/file1.cpp", "src/file2.cpp"],
    includes = ["include"],
    copts = select({
        "//conditions:default": ["-std=c++17"],
        "//conditions:windows": ["/EHsc", "/std:c++latest"],
    }),
    linkopts = ["-Lpath/to/my", "-lmy_dependency"],
)

# Define the executable target
cc_binary(
    name = "my_executable",
    srcs = ["src/main.cpp"],
    deps = [":my_library"],
)

# Set a custom output directory for the library target
filegroup(
    name = "my_library_files",
    srcs = [":my_library"],
    visibility = ["//visibility:private"],
)
exports_files(["BUILD"])
cc_library(
    name = "my_library",
    visibility = ["//visibility:public"],
    data = [":my_library_files"],
    strip_include_prefix = "path/to",
    strip_prefix = "path/to",
    linkstatic = True,
    linkshared = False,
)
filegroup(
    name = "lib_files",
    srcs = [":my_library"],
    visibility = ["//visibility:public"],
)
filegroup(
    name = "include_files",
    srcs = glob(["include/**/*.h"]),
    visibility = ["//visibility:public"],
)
exports_files(["include"])
exports_files(["lib"])
exports_files(["BUILD"])
filegroup(
    name = "include",
    srcs = glob(["include/**/*.h"]),
    visibility = ["//visibility:public"],
)

Buck (Facebook)

cxx_library(
    name = 'my_library',
    srcs = [        'src/file1.cpp',        'src/file2.cpp',    ],
    deps = [        ':my_dependency',    ],
    includes = [        'include',    ],
    copts = select({
        'windows': [
            '/EHsc',
            '/std:c++latest',
        ],
        '//conditions:default': [],
    }),
    linkerflags = [        '-Wl,-rpath,$ORIGIN',    ],
    visibility = [        'PUBLIC',    ],
    link_style = 'static',
    link_whole = True,
)

cxx_library(
    name = 'my_dependency',
    linkstyle = 'static',
    link_whole = True,
    link_libs = [        'path/to/my/lib',    ],
    visibility = [        'PUBLIC',    ],
)

cxx_binary(
    name = 'my_executable',
    srcs = [        'src/main.cpp',    ],
    deps = [        ':my_library',    ],
    visibility = [        'PUBLIC',    ],
)

# Set a custom output path
genrule(
    name = 'copy_library',
    srcs = [        ':my_library',    ],
    outs = [        'build/lib/libmy_library.a',    ],
    cmd = 'mkdir -p $$(dirname $OUT) && cp $< $OUT',
)

Fastbuild

Settings(
    CompilerPath = 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.28.29910/bin/Hostx86/x86',
    LinkerPath = 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.28.29910/bin/Hostx86/x86',
    LibraryPaths = [ 'path/to/my' ],
    IncludePaths = [ 'include' ]
)

Library('my_dependency',
    Sources = [],
    Libs = [ 'llib' ],
    Type = 'Static'
)

Library('my_library',
    Sources = [ 'src/file1.cpp', 'src/file2.cpp' ],
    Libs = [ ':my_dependency' ],
    Type = 'Static'
)

if (TargetEnvironment == 'windows')
{
    SetTargetProperties(
        'my_library',
        OutputDirectory = 'build',
        OutputFilename = 'libmy_library.lib',
        CompilerOptions = '/EHsc /std:c++latest',
        LinkerOptions = '-Wl,-rpath,$ORIGIN'
    )
}
else
{
    SetTargetProperties(
        'my_library',
        OutputDirectory = 'build',
        OutputFilename = 'libmy_library.a'
    )
}

Executable('my_executable',
    Sources = [ 'src/main.cpp' ],
    Libs = [ ':my_library' ]
)

if (TargetEnvironment == 'windows')
{
    SetTargetProperties(
        'my_executable',
        OutputDirectory = 'build',
        OutputFilename = 'my_executable.exe',
        CompilerOptions = '/EHsc /std:c++latest',
        LinkerOptions = '-Wl,-rpath,$ORIGIN'
    )
}
else
{
    SetTargetProperties(
        'my_executable',
        OutputDirectory = 'build',
        OutputFilename = 'my_executable'
    )
)

xmake

-- Define the project and its language
add_rules('mode.debug', 'mode.release')
target('my_project')
    set_languages('cxx17')

-- Set the source files for the library
add_files('src/file1.cpp', 'src/file2.cpp')

-- Add a dependency on another static library
add_deps('my_dependency')
add_linkdirs('path/to/my')
add_links('lib')

-- Set compiler flags for MSVC on Windows
if is_plat('windows') then
    add_cxxflags('/EHsc', '/std:c++latest')
end

-- Set a custom include path
add_includedirs('include')

-- Set a custom output path
set_targetdir('build')
set_objectdir('build/obj')
set_targetname('my_library')
set_targetprefix('lib')
add_ldflags('-Wl,-rpath,$ORIGIN')

-- Define an executable target and link against the library
target('my_executable')
    set_kind('binary')
    add_files('src/main.cpp')
    add_deps('my_library')

Build executors

These build systems actually build your projects, but they cannot generate a project for your IDE or for another build system.

Boost.Build (B2)

This one is provided by the actual intelligence, not an artificial one - thank you to grafikrobot on Reddit!

project my_project ;

# Add a dependency on another static library
lib my_dependency : : <file>path/to/my/lib.a ;

# Set the target name..
lib my_library
    :   # ..and source files for the library
        src/file1.cpp src/file2.cpp
        # Add a dependency on another static library
        my_dependency
    :   # Set compiler flags for MSVC on Windows
        # (for /EHsc option)
        [ conditional <toolset>msvc : <exception-handling>on
            <asynch-exceptions>off <extern-c-nothrow>on ]
        # (for /std:c++latest)
        <cxxstd>latest
        # Set a custom output path
        <location>lib
    :
    :
        # Set a custom include path (as a usage requirement)
        <include>include
    ;

# Define an executable target and link against the library
exe my_executable : src/main.cpp my_library/<link>static ;

Jam

# Set the project name and version
project my_project : version 1.0 ;

# Set the C++ toolset
toolset gcc ;

# Set the source files for the library
lib my_library : src/file1.cpp src/file2.cpp ;

# Add a dependency on another static library
lib my_dependency : : <file>path/to/my/lib.a ;
use-project /my_dependency ;

# Set compiler flags for GCC on Linux
if $(os) = LINUX
{
    CFLAGS += -std=c++17 ;
}

# Set compiler flags for MSVC on Windows
if $(os) = NT
{
    CFLAGS += /EHsc /std:c++latest ;
}

# Set a custom include path
INCLUDES += include ;

# Set a custom output path
lib my_library
{
    LOCATE lib ;
    BINDIR $(CWD)/build/lib ;
}

# Define an executable target and link against the library
exe my_executable : src/main.cpp : my_library ;

Makefile

CC = g++
AR = ar

CFLAGS = -Wall -Wextra -Wpedantic
ifeq ($(OS),Windows_NT)
    CFLAGS += -std=c++17 -m64
else
    CFLAGS += -std=c++17
endif

INCLUDES = -Iinclude

LDFLAGS =

LIBRARY_OUTPUT_PATH = $(CURDIR)/lib

SRCS = src/file1.cpp src/file2.cpp
OBJS = $(SRCS:.cpp=.o)

all: my_executable

libmy_library.a: $(OBJS)
	$(AR) rcs $(LIBRARY_OUTPUT_PATH)/$@ $(OBJS)

%.o: %.cpp
	$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@

my_executable: src/main.cpp libmy_library.a
	$(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS) src/main.cpp -o my_executable -L$(LIBRARY_OUTPUT_PATH) -lmy_library

clean:
	rm -f $(LIBRARY_OUTPUT_PATH)/libmy_library.a $(OBJS) my_executable

Ninja

cxx = c++
cxx_flags = -Wall -Wextra -Wpedantic

# Targets

build my_library.a: cxx_library | src/file1.cpp src/file2.cpp
  ldflags = 
  include_directories = include
  lib = 
  linkflags = 
  out = my_library.a
  pic = 
  position_dependent_code = False
  soname = 
  sources = src/file1.cpp src/file2.cpp

build my_dependency.a: phony
  ldflags = 
  include_directories = 
  lib = path/to/my/lib.a
  linkflags = 
  out = my_dependency.a
  pic = 
  position_dependent_code = False
  soname = 

build my_executable: cxx_link | src/main.cpp my_library.a
  ldflags = 
  include_directories = 
  lib = my_library.a
  linkflags = 
  out = my_executable
  pic = 
  position_dependent_code = False
  soname = 
  sources = src/main.cpp

Gradle

plugins {
    id 'cpp'
}

model {
    components {
        my_library(NativeLibrarySpec) {
            sources {
                cpp {
                    source {
                        srcDirs "src"
                        include "**/*.cpp"
                    }
                }
            }

            binaries {
                if (toolChain in VisualCpp) {
                    staticLibrary {
                        libFile = "my_library.lib"
                        cppCompiler.args "/EHsc", "/std:c++latest"
                    }
                } else {
                    staticLibrary {
                        libFile = "libmy_library.a"
                        cppCompiler.args "-std=c++17"
                    }
                }
            }

            buildTypes {
                release {
                    cppCompiler.args "-O2"
                }
            }

            targetPlatform "x86_64"
            targetPlatformVersion "10.0"
            targetMachines "machine1"
            targetDirectory "build"
        }

        my_executable(NativeExecutableSpec) {
            sources {
                cpp {
                    source {
                        srcDirs "src"
                        include "**/*.cpp"
                    }
                }
            }

            binaries.all {
                if (toolChain in VisualCpp) {
                    linker.args "/SUBSYSTEM:CONSOLE", "/ENTRY:mainCRTStartup"
                }
            }

            linkLibraries "my_library"
        }
    }
}

Scons 4.2.0

env = Environment()

# Set compiler flags for MSVC on Windows
if env['PLATFORM'] == 'win32':
    env.Append(CCFLAGS=['/EHsc', '/std:c++latest'])

# Set a custom include path
env.Append(CPPPATH=['include'])

# Set a custom output path
env.Append(LIBPATH=['build'])
env.Append(LINKFLAGS=['-Wl,-rpath,$ORIGIN'])
env.Append(LIBPREFIX='lib')

# Set the target name and source files for the library
my_library = env.StaticLibrary(target='my_library', 
    source=['src/file1.cpp', 'src/file2.cpp'])

# Add a dependency on another static library
my_dependency = env.StaticLibrary(target='my_dependency', 
    source=[], 
    LIBPATH=['path/to/my'], 
    LIBS=['lib'])

my_library.Depends(my_library, my_dependency)

# Define an executable target and link against the library
my_executable = env.Program(target='my_executable', 
    source='src/main.cpp', 
    LIBS=[my_library])

<
Blog Archive
Archive of all previous blog posts
>
Next Post
This curious typo could be a serious bug