Skip to content

Instantly share code, notes, and snippets.

@ricky9w
Created April 14, 2022 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ricky9w/219cfe532235081b61f43f9d1776fbea to your computer and use it in GitHub Desktop.
Save ricky9w/219cfe532235081b61f43f9d1776fbea to your computer and use it in GitHub Desktop.
Basic concepts of software building tools, introduction to Make and CMake

Introduction to Software Building

Introduction

Make, Ninja, Ant, Gradle, MSVC are some of the build systems. They help us to drive the compiler and other build tools. You may need to write a ‘build file’ to tell them the dependency information for them to build a software.

While this is already very convenient, the building of C/C++ projects is not standard across the platforms. Then you may need a ‘meta-build system’ to help you generate ‘build’ files for different build systems.

CMake is such a ‘meta-build system’ that can generate platform based system files by setting some flags.

See this article about the details of a buildsystem: What Is a Build System? - Scott Dorman

More information about the difference between Make and CMake: What is the difference between CMake and Make? - Quora

CMake vs. Make - Incredibuild

c++ - Difference between using Makefile and CMake to compile the code - Stack…

Mastering Makefile

Introduction

Make is a buildsystem - it drives the compiler and other build tools to build your project. It can also help when you have a series of instructions to run depending on what file have changed.

Basics

Structure

A Makefile consists of a series of rules:

target ...: prerequisites ...
	recipe
	...

You can define multiple targets for a rule, the commands will be run for every single target. $@ is an automatic variable that contains the target name:

all: f1.o f2.o

f1.o f2.o:
	echo $@
# Equivalent to:
# f1.o:
#	 echo f1.o
# f2.o:
#	 echo f2.o

Variables

Variables can only be strings. You can use := and = to define variables, and refer to them with ${} or $():

x := dude

all:
	echo $(x)
	echo ${x}

  # Bad practice, but works
	echo $x 

Wildcard

Both * and % are called wildcards in Makefile, but mean different things.

* searches your filesystem for matching filenames. You can use it in the wildcard function:

thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)

all: one two three four

# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)

# Stays as *.o if there are no files that match this pattern :(
two: *.o 

# Works as you would expect! In this case, it does nothing.
three: $(thing_right)

# Same as rule three
four: $(wildcard *.o)

Note that * may not be directly used in variable definitions, and when it matches no files, it is left as it is (unless in the wilecard function).

Automatic Variables

There are many automatic variables:

  • $@: All the targets
  • $^: All the prerequisites
  • $<: The first prerequisite
  • $?: All the prerequisites newer than the target
  • $(@D): The directory of the target

You can use these automatic variables to simplify your Makefile.

An example:

.PHONY: binary

binary: build/binary

build/%.o: src/%.c
	mkdir -p $(@D)
	g++ $^ -o $@

build/binary: build/a.o build/b.o
	cat $^ > $@

Implicit Rules

Commands and Execution

Add an @ before a command to stop it from being printed:

all:
	@echo: "This command will not be printed"

Each command is run independently except for this:

all: 
	cd ..
  # The cd above does not affect this line, because each command is effectively run in a new shell
	echo `pwd`

  # This cd command affects the next because they are on the same line
	cd ..;echo `pwd`

  # Same as above
	cd ..; \
	echo `pwd`

The default shell is /bin/sh, and you can change that by setting the SHELL variable:

SHELL=/bin/bash

Error Handling

You can handle errors of commands by adding some options to these commands:

  • -k: let Make continue and output all the errors together
  • -: supress the errors

Recursive Make

Use $(MAKE) to make recursively:

new_contents = "hello:\n\ttouch inside_file"
all:
	mkdir -p subdir
	printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
	cd subdir && $(MAKE)

clean:
	rm -rf subdir

Phony Targets

A phony target is not really the name of a file, but just a name of a recipe to be executed. When making phony targets, Make will just run its recipe without checking if the file exsists. For example, the following definition of clean will avoid clean work not run when you have a file named clean in your working directory:

.PHONY: clean
clean:
	rm *.o

Into Practice

Assume that you have such a Makefile:

buid/a.o: src/a.c
	mkdir -p build
	g++ src/a.c -o build/a.o

build/b.o: b.c
	mkdir -p build
	g++ src/b.c -o build/b.o

build/binary: build/a.o build/b.o
	cat build/a.o build/b.o > build/binary

You can turn it into a prettier one by using variables and functions:

.PHONY: binary

SRC_DIR := src/
SRCS := $(shell find $(SRC_DIR) -name "*.o")
OBJ_DIR := build/
OBJS := $(patsubst src/%.c,build/%.o,$(SRCS))

binary: $(OBJ_DIR)binary

$(OBJ_DIR)%.o: $(SRC_DIR)%.c
	mkdir -p $(@D)
	g++ $^ -o $@

$(OBJ_DIR)binary: $(OBJS)
	cat $^ > $@

Tweaks

You can see what commands are executed with make -nB target.

You can output some info with $(info "xxx") to debug a Makefile.

Conclusion

Refer to this tutorial for more information about how to write a Makefile: Makefile Tutorial By Example

Go to GNU.org for the complete tutorial to Make: GNU make

See here for more information in practice: https://www.cs.swarthmore.edu/~newhall/unixhelp/howto_makefiles.html

Fragments

You can use patsubst or the OBJS = $(SRCS:.c=.o) grammar to to string substitute

Look for more Makefile templates here: 几个Makefile通用模板分享!-面包板社区

Introduction of how to use gcc and make: Gcc tutorial GCC and Make - A Tutorial on how to compile, link and build C/C++ applications

Mastering CMake

Introduction

CMake is a buildsystem generator. It is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler envirionment of your choice.

If you have a cross-platform project, then CMake is a powerful tool to make your project buildsystem-independent as well.

The CMake process:

  • Configure:
    • Parse top level CMakeLists.txt
    • Generate cache variables
  • Generate Generate native build tool files
  • Build Use native bulid tools to compile the sources

Conclusion

Refer to this tutorial for more information: An Introduction to Modern CMake · Modern CMake

CMake Howto

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment