# GCC/Clang on Linux/macOS
$ g++ hello.cpp
$ ./a.out
$ g++ -std=c++20 -Wall hello.cpp -o hello
$ ./hello
# MSVC on Windows
> cl /EHsc /std:c++17 /W3 hello.cpp
> hello.exe
-o-std=c++14 optional-Wall best practise optionalg++ and cl are just clang++) copies GCC’s flags
A useful
wizardryskill even today.
#include <iostream> // preprocessor
float perimeter(float radius); // compiler
extern float PI; // linker
int main() { // linker
constexpr auto radius = 2.71828f; // compiler
std::cout << perimeter(radius); // compiler + linker
}
| Add | Include Dir (Preprocessor) | Library Dir (Linker) |
|---|---|---|
| Directly | -I./inc |
-L./lib |
| Indirectly | -Xpreprocessor -I -Xpreprocessor ./inc |
-Xlinker -L -Xlinker ./lib |
$ g++ -std=c++17 -c hello.cpp
$ ls
hello.cpp hello.o
$ nm hello.o
0000000000000000 T greet
> cl /EHsc /c hello.cpp
> dir
hello.cpp hello.obj
> dumpbin /symbols hello.obj
.o.obj1: Don’t conflate with entire machine VMs like Hyper-V, VirtualBox, …
# How do I find the OS/architecture of some rogue binary?
# Linux
$ file my_bin
my_bin: ELF 64-bit, x86-64, GNU/Linux 3.2.0, stripped
# macOS
$ file my_bin
my_bin: Mach-O 64-bit executable x86_64
# Windows (MSYS2 or WSL2)
> file my_bin.exe
my_bin.exe: PE32+ executable (console) x86-64, for MS Windows
int main()WinMain.h, .hpp, .hxx, …) compiler.a, .lib, .so, .dll, …) linker ospng.h + libpng.a)
| OS Family | Extension | Format |
|---|---|---|
| Unix | none | Executable & Linkable Format (ELF) |
| Windows | .exe |
Portable Executable (PE/PE32+) |
| macOS | none | Mach object (Mach-O) |
+---------------------+----------+ +--------------+ +--------------+
| | | | | | |
| | | | | | |
| Application 1 | Static | | Application | | Application |
| | Lib A | | 3 | | 4 |
| | | | | | |
+---------------------+----------+ +------\-------+ +------/-------+
\ /
\ /
+---------------------+----------+ +----\------------/-----+
| | | | |
| | | | |
| Application 2 | Static | | Shared Library B |
| | Lib A | | |
| | | | |
+---------------------+----------+ +-----------------------+
$ ar -rcs libTrig.a sin.o cos.o tan.o
$ ar -t libTrig.a
sin.o cos.o tan.o
$ nm libTrig.a
0000000000000000 T sin
0000000000001000 T cos
$ ls -l
80K libTrig.a
20K libmath.a
200K tool.o
$ gcc -o tool tool.o libTrig.a
ld: sin.o: undefined reference to 'add(int, int)'
$ gcc -o tool tool.o -ltrig -lmath
$ ls -l tool
300K tool
trig.h compilermain(), DllMain(), …| No “missing dependencies” error for app | No sweeping updates / fixes |
| No version mismatches or Dependency Hell | Every app to rebuild on update |
| Single executable; simpler package/install | Disk space (fat binaries, multiple copies)¹ |
| Apps may ignore breaking lib version | No on-demand loading / plug-ins |
| Library needn’t be backward-compatible | Slower build time for app (strip unused) |
1: Doesn’t apply to Windows; each software brings its own (non-system) libraries
$ g++ -o tool tool.o
$ ls -l
200K tool.o
200K tool
$ g++ -shared -fPIC {sin,cos,tan}.cpp -o trig.dll -lmath
$ nm trig.dll
0000000000000000 T sin
0000000000001000 T cos ...
$ gcc -o tool tool.o trig.dll
$ ls -l
80K trig.dll
200K tool.o
200K tool
a.dll → b.dll → … 😲 dependency chaindlopen, LoadLibraryDllMain
| OS | Name |
|---|---|
| Windows | Dynamic Link Libraries (.dll) |
| Linux | Shared Objects (.so) |
| macOS | Dynamic Shared Libraries/Bundles (.dylib / .so) |
| Sweeping updates / fixes | Missing dependencies; failure to launch |
| Plug-ins / on-demand loads | Versioning / Dependency Hell |
| Toolchain independent; cross-toolchain | OS dependent |
| No app rebuilding | Many OS-specific binaries; pkg/install hassle |
| Lesser disk footprint | Backward-compatible considerations |
| Forced updates breaking app |
# GCC/MinGW on Windows
> g++ -std=c++17 -D_DEBUG hello.cpp -g -O0 -flto -o hello.exe
# MSVC on Windows
> cl /EHsc /std:c++17 /D_DEBUG hello.cpp /Zi /Od /LTCG
-g-O0-flto-D_DEBUG → #define _DEBUG-DPI=3.14 → #define PI 3.14Conditional compilation of certain pieces of code.
# 2. Conditional Inclusion
# BUILD.gn
if (is_linux || is_chromeos) {
sources += [
"base_paths_posix.cc"
]
}
// 1. Macro
// C++
#if defined(ENABLE_TAB_TOGGLE)
tab_toggler.init();
#endif
Configuration: particular combination of all switches¹.
m × n switches (toolchain × software)
m x n isn’t possible
| Emojis | Speech | Plugins | Logging | Debug | Optimization | |
|---|---|---|---|---|---|---|
| Config1 | ✓ | ✓ | ✓ | ✓ | ||
| Config2 | ✓ | ✓ | ✓ |
1: Think: args.gn
$ cd ~/edge/src
$ gn args out/release_x64 --list --short | wc -l
887
$ wc -l < out/release_x64/args.gn
11
$ gn args out/release_x64 --list --short --overrides-only | wc -l
20
$ gn args out/release_x64 --list=crashpad_dependencies
crashpad_dependencies
Current value = "chromium"
From //.gn:51
Overridden from the default = "standalone"
From //third_party/crashpad/crashpad/build/crashpad_buildconfig.gni:19
make, BSD make; MSVC: nmake# commonly used flags in variable
CXXFLAGS = -std=c++17 -Wall
LDFLAGS = -flto
biryani: rice.o spices.o
g++ $(LDFLAGS) -o biryani rice.o spices.o
cp biryani ./installer/bin
spices.o: spices.cpp spices.h
g++ $(CXXFLAGS) -o spices.o -c spices.cpp
rice.o: rice.cpp rice.h utensils.h spices.h
g++ $(CXXFLAGS) -o rice.o -c rice.cpp
clean:
rm -rf biryani *.o
.PHONY: clean
Makefile at project root with rulesmake: build first target
make TARGET: only build TARGET (and its dependencies).PHONY; always outdated1: older than a dependency
# commonly used flags in variable
CXXFLAGS = $(USERFLAGS) -std=c++17 -Wall
LDFLAGS = -flto # LTO ON
LDLIBS = -lz -lmath # libMath.a, libZ.a
biryani: rice.o spices.o
$(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS)
cp biryani ./installer/bin
spices.o: spices.cpp spices.h
rice.o: rice.cpp rice.h utensils.h spices.h
doc: ref.html tutorial.html
# Pattern rule
%.html: %.md
pandoc -o $@ $<
# e.g. pandoc -o ref.html ref.md
clean:
$(RM) biryani *.o
commit:
git add $(wildcard *.cpp *.h)
git commit
.PHONY: clean doc commit
Makefilemake CC=clang++: override toolchain to ClangUSER_FLAGS='-DMY_SHINY_FEATURE=ON -O3' make$@, dependencies $^, first dep: $<.X → .Y.o from .cpp, .c, etc.
$(CC) -c $(CFLAGS) -o $@ $<make doc builds documentation using Pandocmake install: bunch of copies.POSIX:
COMPILER_FLAGS = -Wall -Werror -pedantic -pedantic-errors
CXXFLAGS = -std=c++17 $(COMPILER_FLAGS)
all: debug release
debug: CXXFLAGS += -g -O0 -D_DEBUG -DDEBUG
debug: hello
release: CXXFLAGS += -O2 -DNDEBUG
release: hello
hello: hello.swift MyCMod/adder.o
swiftc -I . -o $@ $<
MyCMod/adder.o: MyCMod/adder.cpp MyCMod/adder.h
clean:
$(RM) hello MyCMod/adder.o
.PHONY: all clean
make debug and make releasemake to build both
all targetbuild.ninja
“[…] designed to have its input files generated by a higher-level build system. Ninja build files are human-readable but not especially convenient to write by hand.”
executable("img_view") { # target
sources = [
"window.cpp",
"filter.cpp",
]
cflags = [ "-Wall" ] # flags
defines = [ "USE_GPU=1" ] # feature macros
include_dirs = [ "./inc" ]
dependencies = [
":libpng", # in-file
# ‘core’ under third_party/animator/BUILD.gn
"//third_party/animator:core" # qualified
# ‘markdown’ under third_party/markdown/BUILD.gn
"//third_party/markdown" # implicit
]
if (is_win) {
sources += [ "d3d11.cpp" ]
sources -= [ "window.cpp" ]
ldflags = [ "/SUBSYSTEM:WINDOWS",
"/DELAYLOAD: d3d11.dll" ]
}
}
static_library("libpng") {
sources = [
"encoder.c",
"decoder.c",
]
public_deps = [
"//third_party/boost:file_io"
]
}
executable, static_library, shared_libraryloadable_module, source_set raresources: define (= [ … ]), add (+=) or remove (-=)cflags / ldflags: compiler or linker flagsdefines: (feature) macros":base"
| Executable | Static | Shared | Loadable Module | Source Set | |
|---|---|---|---|---|---|
| Windows | .exe |
.lib |
.dll |
.dll |
.obj |
| Linux | none | .a |
.so |
.so |
.o |
| macOS | none | .a |
.dylib |
.so |
.o |
# A can use B and C but not super_secret
executable("A") {
deps = [ ":B" ]
}
shared_library("B") {
public_deps = [ ":C" ]
deps = [ ":super_secret" ]
# link no code from evil directory
assert_no_deps = [ "//evil/*" ]
}
A → B → C
dependencies: B can include/use C; A can’tpublic_deps: A can include C tooB should publicly depend on C if it’s part of interface Bdeps and public_deps to final targetpublic_depsassert_no_deps: disallow targets from linkingdeclare_args() {
enable_command_line = false
use_opengl = true
assert(!(use_opengl && enable_command_line),
"Can’t use OpenGL and terminal together")
}
config("memory_tagging") {
if (current_cpu == "arm64" && is_linux) {
cflags = [ "-march=armv8-a+memtag" ]
}
}
executable("img_view") {
if (use_opengl) {
ldflags += [ "/DELAYLOAD: opengl32.dll" ]
}
configs += [ ":memory_tagging" ]
}
shared_library("cpu_filters") {
sources = [ "shaders.cpp" ]
configs += [ ":memory_tagging" ]
if (use_opengl) {
# using GPU, skip tagging CPU memory
configs -= [ ":memory_tagging" ]
}
}
declare_args: define arguments for your target
args.gn, command-line or toolchain argsconfig: distil common configuration for reuse
public_config to propagate up the dependency chainA inherits public_configs of C tooall_dependent_configs: force configs on dependants rare
-=) these configsdata: Runtime data dependencies
data_deps: non-linked runtime dependencies
gn desc TARGET lists in runtime_deps sectiongn --runtime-deps-list-file=INPUT
OUTPUT.runtime_deps in target’s output directoryaction("run_this_guy_once") {
script = "doprocessing.py"
sources = [ "my_configuration.txt" ]
outputs = [ "$target_gen_dir/some_output.txt" ]
# doprocessing.py imports this script; rebuild if it changes
inputs = [ "helper_library.py" ]
# root_build_dir is script’s working dir
args = [ "-i", rebase_path(inputs[0], root_build_dir),
rebase_path(outputs[0], root_build_dir) ]
}
copy("mydll") {
sources = [ "mydll.dll" ]
outputs = [ "$target_out_dir/mydll.dll" ]
}
Useful for pre-/post-build tasks
action: target to run script onceaction_forach: run over set of filescopy: target to copy files
$ cd out/debug_x64
$ gn ls . '//base/*' # list all base targets
//base:base
//base:base_paths
//base:base_static
//base:build_date
//base:build_utf8_validator_tables
//base:check_example
//base:debugging_flags
//base:i18n
$ gn ls . # list all targets under all paths
# list only static libraries under //base; --type understand all 5 artefacts
$ gn ls . --type=static_library '//base/*'
//base:base
//base:base_static
//base:i18n
# ※ get the actual target to feed ninja ※
$ gn ls . --type=static_library --as=output '//base/*'
obj/base/libbase.a
obj/base/libbase_static.a
obj/base/libbase_i18n.a
$ autoninja obj/base/libbase_static.a # build only libbase_static.a
# ※ what if I want to build just one .cc? drop to ninja level ※
$ ninja -t targets all > all_targets.txt
$ grep 'browser_window_ring' all_targets.txt # Windows: findstr /srip /C:
obj/chrome/browser/ui/ui/browser_window_ring_touch_bar.o: objcxx
obj/chrome/test/unit_tests/browser_window_ring_touch_bar_unittest.o: objcxx
$ autoninja obj/chrome/browser/ui/ui/browser_window_ring_touch_bar.o
# Why can’t I include a header from dependency X? X isn’t a public_dep.
$ gn path . --public //components/history/content/browser //chrome/browser
No public paths found between these two targets.
# find path and depend on a target; include headers
$ gn path . //cc/base //content/browser
//content/browser:browser --[public]-->
//services/viz/public/mojom:mojom --[public]-->
//cc/paint:paint --[public]-->
//cc/base:base
# print dependency tree
$ gn desc . //tools/gn deps --tree
//base:base
//base:base_paths
//base:base_static
//base:build_date
//base:copy_dbghelp.dll
//base:debugging_flags
//base/allocator:allocator
//base/allocator:allocator_shim
//base/allocator:prep_libc
# where did that flag come from?
$ gn desc . //base cflags --blame
From //build/config/compiler:default_optimization
(Added by //build/config/BUILDCONFIG.gn:456)
/Od
/Ob0
/RTC1
From //build/config/compiler:default_symbols
(Added by //build/config/BUILDCONFIG.gn:457)
/Zi
gn check .
ERROR at //base/files/file_path.cc
#include "sql/statement.h"
^--------------
It is not in any dependency of
//base:base
The include file is in the target(s):
//sql:sql
which should somehow be reachable.
gn help: built-in help
gn help ls, gn help root_out_dirgn ls: list targetsgn desc: describe targets
--tree and --blamegn path: dependency path from two targetsgn args: query current build’s argumentsgn clean: keep only args.gn and Ninja filestemplate("grit") {
...
}
grit("components_strings") {
source = "components.grd"
output = [ ... ]
}
template("component") {
if (is_component_build) {
_component_mode = "shared_library"
} else if (defined(invoker.static_component_type)) {
assert(invoker.static_component_type == "static_library" ||
invoker.static_component_type == "source_set")
_component_mode = invoker.static_component_type
} else if (!defined(invoker.sources) || invoker.sources == []) {
# When there are no sources defined, use a source set to avoid creating
# an empty static library (which generally don't work).
_component_mode = "source_set"
} else {
_component_mode = "static_library"
}
}
component("base") {
# sources, flags, etc.
}
.gni files to import
component (//build/config/BUILDCONFIG.gn)
debugmsvc_toolchain (//build/toolchain/win/BUILD.gn)clang_toolchain (//build/toolchain/gcc_toolchain.gni)apple_toolchain (//build/toolchain/apple/toolchain.gni).gn at project root
gn help dotfile//build/config/BUILDCONFIG.gn
is_win, {target,host}_os, {target,host}_cpu, …)gn help set_defaults
set_defaults(static_library) { configs = [ ":def_flags", ":optimize" ] }# gn.googlesource.com/gn/+/main/examples/simple_build/build/toolchain/BUILD.gn
toolchain("gcc") {
tool("cc") {
command = "gcc {{defines}} {{include_dirs}} {{cflags}} {{cflags_c}} -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC {{output}}"
outputs =
[ "{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o" ]
}
tool("cxx") {
description = "CXX {{output}}"
# ... snipped ...
}
tool("alink") {
command = "rm -f {{output}} && ar rcs {{output}} {{inputs}}"
description = "AR {{target_output_name}}{{output_extension}}"
outputs =
[ "{{target_out_dir}}/{{target_output_name}}{{output_extension}}" ]
default_output_extension = ".a"
output_prefix = "lib"
}
tool("solink") {
description = "SOLINK $soname"
# ... snipped ...
}
tool("link") {
outfile = "{{target_output_name}}{{output_extension}}"
# reponse file to hold command until successful execution
rspfile = "$outfile.rsp"
rspfile_content = "{{inputs}}"
command = "g++ {{ldflags}} -o $outfile @$rspfile {{solibs}} {{libs}}"
description = "LINK $outfile"
default_output_dir = "{{root_out_dir}}"
outputs = [ outfile ]
}
tool("stamp") {
command = "touch {{output}}"
description = "STAMP {{output}}"
}
tool("copy") {
command = "cp -af {{source}} {{output}}"
description = "COPY {{source}} {{output}}"
}
}
OS, CPU, etc.set_default_toolchain if > 1//build/toolchain