# 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
.obj
1: 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
, LoadLibrary
DllMain
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.14
Conditional 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
Makefile
make 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 release
make
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_library
loadable_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 B
deps
and public_deps
to final targetpublic_deps
assert_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_dir
gn ls
: list targetsgn desc
: describe targets
--tree
and --blame
gn 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
)
debug
msvc_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