Lab 1 The Software build process

Aims

The aim of this lab is to introduce the C++ software build process, this will include.

  1. Single file software build
  2. Using external libraries and package managers (vcpkg)
  3. Build Tools (make,cmake)

Getting Started

We will start this project in a new folder (here we are assuming using the windows powershell and developer console)

From here we are going to use the New-Item powershell command which is similar to the Linux touch command. We use this to create an empty file and then open it in Visual Studio Code. At this point it may be worth reading up on the use of C++ in VS Code from here and installing the C/C++ extensions

We are now going to create a simple “hello world” program by adding the following code to the file hello.cpp

#include <iostream>
#include <cstdlib>

int main()
{
  std::cout<<"hello world \n";
  return EXIT_SUCCESS;
}

Remember to save the file before we compile it (CTRL + s) then we can compile and run the program as follows.

cl main.cpp /W0 /std:c++17 /EHsc /Fe"Hello"

Under linux we can use the following

g++ -Wall -g -std=c++17 hello.cpp -o Hello

Compiler flags

In the previous example we used the following flags

flaguse
/W0Suppress all warnings (I will discuss this more later)
/std:c++17enable c++17
/Fe"[name]"output to executable [name]
/EHscset the default exception handling model

Under Linux we can use similar

flaguse
-Wallenable all warnings
-genable debug
-std=c++17set c++ 17
-o [name]output to executable [name]

Package Management

In our current lab build we have a number of C++ libraries installed using the vcpkg package manager.

You will need to install this on your local machine if you wish to use it as follows

> git clone https://github.com/microsoft/vcpkg
> .\vcpkg\bootstrap-vcpkg.bat

This tool allows us to install and build libraries and include them using a number of build tools on a number of platforms.

You can install your own version at home by following the instructions here, once installed we can install the fmt library we are going to use in this example by changing to the vcpkg directory and running

./vcpkg install fmt:x64-windows

We are going to create another empty file called fmt.cpp

and add the following code which is using the fmt:: library, which contains an implementation of the new c++ 20 format library amongst other things.

#include <fmt/format.h>
#include <cstdlib>
int main()
{
  fmt::print("This is using placeholders {} {} {} \n",1,2,3);
  return EXIT_SUCCESS;
}

If we attempt to compile this as we have done with the previous examples we get the following.

cl .\fmt.cpp /W3 /std:c++17 /EHsc /Fe"fmt"

Under linux we use

g++ -Wall -g -std=c++17 fmt.cpp -o fmt

The reason we get this error is because we have not told g++ where to find the header files. By default g++ uses the following (in the University linux labs1).

gcc -xc++ -E -v -

/public/devel/2020/include /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7
/opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/x86_64-redhat-linux
/opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/../../../../include/c++/7/backward
/opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7/include
/usr/local/include
/opt/rh/devtoolset-7/root/usr/include
/usr/include

As the fmt library was installed using vcpkg we need to tell the compiler where to search. In the case of the university it is the following and we use the -I flag to tell the compiler where to search for include files.

In Windows powershell we can use $HOME to indicate our home directory where we have installed vcpkg.

cl .\fmt.cpp /W3 /std:c++17 /EHsc /I $HOME\vcpkg\installed\x64-windows\include /Fe"fmt"

Under linux we use the following.

g++ -Wall -g -std=c++17 -I /public/devel/2020/vcpkg/installed/x64-linux/include/ fmt.cpp -o fmt

You will now notice that whilst the error with the inclusion of <fmt/format.h> has gone we now get a new error from not the compiler but the linker (ld). In this case the error is an “undefined reference to” error which usually signifies that the linker has looked for a function defined in a header file but but can’t find it. We now have to tell the compiler / linker where to find the libraries and which ones to add (link). This is done as follows

cl .\fmt.cpp /W3 /std:c++17 /EHsc /I $HOME\vcpkg\installed\x64-windows\include /Fe"fmt" /link /LIBPATH $HOME\vcpkg\installed\x64-windows\lib\fmt.lib

Under Linux we use

g++ -Wall -g -std=c++17 -I//public/devel/2020/vcpkg/installed/x64-linux/include fmt.cpp -L /public/devel/2020/vcpkg/installed/x64-linux/lib -lfmt

You will notice that under windows the executable doesn’t run properly at first. This is due to the program not finding the dll required. To make it work we need to copy the DLL into the same folder as the executable.

In the previous example we used the following flags

flaguse
/I [path]add include search path
/linkeverything that follows are linker options
/LIBPATH [name]add library

Under Linux we use

flaguse
-I [path]add include search path
-L [path]add library search path
-l [name]add library

Libraries

Note the naming convention for unix libraries are as follows :-

graph LR;
  l1(lib)-->l2(Library Name)-->l3(".so | .a")

A .so or .dll file is a dynamic library and a .a / .lib file is a static library. By default vcpkg builds static libraries under linux. When adding them to the linker with the -l flag we omit the lib and the .a .so extensions. By default the linker will use a .so file if found else will search for the .a version. We will look at the implications for this in a future session.

Header Only Libraries

C++ allows use to develop “header only” libraries as well as compiled dynamic and static ones. These do have some advantages as it can make our compilation process easier (we don’t need the -L / -l flags) and as the libraries are added to the compilation process can avoid ABI issues. The disadvantage is the possible increase in compilation time.

The fmt library has the ability to be included as a header only version as follows.

cl .\fmt.cpp /W3 /std:c++17 /EHsc /I $HOME\vcpkg\installed\x64-windows\include /DFMT_HEADER_ONLY /Fe"fmt"

Under Linux we use

g++ -Wall -g -std=c++17 -DFMT_HEADER_ONLY -I/public/devel/2020/vcpkg/installed/x64-linux/include fmt.cpp -o fmt

In the case the /D or -D flag adds a definition to the compiler command line which is the equivalent of using the #define pre-processor macro in C++ so in this case it is like using in the source file.

#define FMT_HEADER_ONLY

Automating the build

As you can see from the above examples there is a lot of typing to repeat each time we wish to build a new project. To help overcome this we can use the make or under windows nmake tool.

Make gets its knowledge of how to build your program from a file called the makefile, which lists each of the non-source files and how to compute it from other files. When you write a program, you should write a makefile for it, so that it is possible to use Make to build and install the program.

We will add the following lines to the Makefile under Windows

CFLAGS = /W3 /std:c++17 /EHsc
DEFINES = /DFMT_HEADER_ONLY
HOME=%HOMEDRIVE%\%HOMEPATH%
INCLUDE_PATH = /I $(HOME)\vcpkg\installed\x64-windows\include
OBJECTS=fmt.obj 

fmt.exe : $(OBJECTS)
	cl.exe  $(OBJECTS) /Fe"fmt"

fmt.obj : fmt.cpp
	cl.exe /c $(CFLAGS) $(INCLUDE_PATH) $(DEFINES) fmt.cpp

clean :
	del *.obj fmt.exe

Under Linux

CFLAGS = -Wall -g -std=c++17
DEFINES = -DFMT_HEADER_ONLY
INCLUDE_PATH = -I /public/devel/2020/vcpkg/installed/x64-linux/include
OBJECTS=fmt.o 

fmt : $(OBJECTS)
	g++  $(OBJECTS) -o fmt

fmt.o : fmt.cpp
	g++ -c $(CFLAGS) $(INCLUDE_PATH) $(DEFINES) fmt.cpp

clean :
	rm -f *.o fmt

The makefile itself is a list of defines ( CFLAGS = -Wall) and a basic “recipe” for how to build the elements. For example the line

fmt : $(OBJECTS)
	g++  $(OBJECTS) -o fmt

Says the file fmt is made from the defines in the variable OBJECT to to build that we need to execute g++ $(OBJECTS) -o fmt

Make is very sensitive to spaces and tabs. In the above example the command lines (the ones below the recipe) are indented with a tab. For more information see here

By default will look for a file called makefile or Makefile however in this case we have named it Makefile.linux so we have to use the following commands to use it.

There is another target added to this makefile called clean which will remove and of the build files and the executable, this is a common practice when using make.

Using cmake

CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake 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 environment of your choice.

One of the advantages of tools like cmake is that it uses a “metalanguage” to create a platform specific makefile (or under windows MSBuild / Visual Studio project). The following examples are based on the book Professional CMake (A Practical Guide) By Craig Scott where he describes CMake thus

Without a build system, a project is just a collection of files. CMake brings some order to this, starting with a human-readable file called CMakeLists.txt that defines what should be built and how, what tests to run and what package(s) to create. This file is a platform independent description of the whole project, which CMake then turns into platform specific build tool project files. As its name suggests, it is just an ordinary text file which developers edit in their favorite text editor or development environment.

First we will create a CMakeLists.txt file in the root of the project directory.

# We will always try to use a version > 3.1 if avaliable
cmake_minimum_required(VERSION 3.2)
# name of the project It is best to use something different from the exe name
project(fmt_build) 

# Here we set the C++ standard to use
set(CMAKE_CXX_STANDARD 17)

# Now we are going to search for our fmt library this will be a .cmake file
# in this case with a name like  fmtConfig.cmake or fmt-config.cmake
find_package(fmt CONFIG REQUIRED)

# Now we add our target executable and the file it is built from.
add_executable(fmt fmt.cpp)

# Finally we need to link our libraries. In this case we specify we want to use 
# The header only version of fmt which will set the flags on the compile line.

target_link_libraries(fmt PRIVATE  fmt::fmt-header-only)

We can now build the program using the following.

The define -DCMAKE_TOOLCHAIN_FILE="$HOME\vcpkg\scripts\buildsystems\vcpkg.cmake" or in the labs -DCMAKE_TOOLCHAIN_FILE=/public/devel/2020/vcpkg/scripts/buildsystems/vcpkg.cmake tells cmake where to look for build information for the current system. In this case vcpkg provides these for us. Typing this each time can become tiresome so it is possible to set this as an environment variable in the .bash_profile file.

export CMAKE_TOOLCHAIN_FILE=/public/devel/2020/vcpkg/scripts/buildsystems/vcpkg.cmake

Or using the Windows set environment variable (note this may need the powershell to be re-started)

And modify our cmake file to check for this environment variable.

# We will always try to use a version > 3.1 if avaliable
cmake_minimum_required(VERSION 3.2)

if(NOT DEFINED CMAKE_TOOLCHAIN_FILE AND DEFINED ENV{CMAKE_TOOLCHAIN_FILE})
   set(CMAKE_TOOLCHAIN_FILE $ENV{CMAKE_TOOLCHAIN_FILE})
endif()


# name of the project It is best to use something different from the exe name
project(fmt_build) 

# Here we set the C++ standard to use
set(CMAKE_CXX_STANDARD 17)

# Now we are going to search for our fmt library this will be a .cmake file
# in this case with a name like  fmtConfig.cmake or fmt-config.cmake
find_package(fmt CONFIG REQUIRED)

# Now we add our target executable and the file it is built from.
add_executable(fmt fmt.cpp)

# Finally we need to link our libraries. In this case we specify we want to use 
# The header only version of fmt which will set the flags on the compile line.

target_link_libraries(fmt PRIVATE  fmt::fmt-header-only)

  1. You will notice that ‘/public/devel/2020/include’ is part of this and this has been setup by me as part of the University build by setting the CPATH environment variable. ↩︎

Next