Mastering CMake (2)

Setting Up Your First CMake Project

cmake_minimum_required(VERSION 3.20)
project(Hello)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_executable(Hello hello.cpp)

project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

cmake_minimum_required(VERSION 3.20.0)
project(Rental CXX)
add_executable(Rental
               main.cpp
               cars/car.cpp  
               # more files in other directories 
)

Working with Targets

Essentially, it’s a recipe that a buildsystem uses to compile a list of files into another file. It can be a .cpp implementation file compiled into an .o object file, a group of .o files packaged into an .a static library, and many other combinations.

All that’s required is an add_executable() command with the name of the executable target and a list of the files that are to be its elements

In CMake, we can create a target using one of three commands:

  • add_executable()
  • add_library()
  • add_custom_target()

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

for build you can specify a target or build all project

--build binary_directroy --target [all|target]

add source to target

add_executable(main main.cpp)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
  target_sources(main PRIVATE gui_linux.cpp)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
  target_sources(main PRIVATE gui_windows.cpp)
endif()

preprocessing definitions

add paths to include

target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [item1...]
  [<INTERFACE|PUBLIC|PRIVATE> [item2...] ...])

add definition

set(VAR 8)
add_executable(defined definitions.cpp)
target_compile_definitions(defined PRIVATE ABC
  "DEF=${VAR}")
int main() {
#if defined(ABC)
    std::cout << "ABC is defined!" << std::endl;
#endif
#if (DEF < 2*4-3)
    std::cout << "DEF is greater than 5!" << std::endl;
#endif
}

compiler flags

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -DNO_DEBUG")
set(CMAKE_CXX_FLAGS "-Wall -Wextra")
set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

Managing Dependencies with CMake

  • A static library has a .a extension on Unix-like systems and .lib on Windows.
  • Shared libraries have a .so extension on Unix-like systems and .dll on Windows.

if you install library like

apt install protobuf-compiler libprotobuf-dev

find_package(Protobuf REQUIRED)
target_link_libraries(main PRIVATE ${Protobuf_LIBRARIES})
target_include_directories(main PRIVATE 
  ${Protobuf_INCLUDE_DIRS}
  ${CMAKE_CURRENT_BINARY_DIR})

find_package(Protobuf REQUIRED) asks CMake to run the bundled FindProtobuf.cmake find-module and set up the Protobuf library for us. That find-module will scan commonly used paths and (because we provided the REQUIRED keyword) terminate if a library is not found. It will also specify useful variables and functions (such as the one on the next line).
target_link_libraries adds libraries (static or shared) found by find_package() to the linking command of our main target.

  • <PKG_NAME>_FOUND
  • <PKG_NAME>_INCLUDE_DIRS or <PKG_NAME>_INCLUDES
  • <PKG_NAME>_LIBRARIES or <PKG_NAME>_LIBRARIES or <PKG_NAME>_LIBS
  • <PKG_NAME>_DEFINITIONS
  • IMPORTED targets specified by the find-module or config-file

find_package(<Name> [version] [EXACT] [QUIET] [REQUIRED])

for cmake files have format <package>Config.cmake

find_package(Foo CONFIG REQUIRED)
target_link_libraries(boo foo)
add_library(MyLib SHARED ./lib/MyLib.cpp)
target_link_directories(MyLib PUBLIC ./lib)
target_include_directories(MyLib PUBLIC ./lib)
find_package(TBB REQUIRED)
target_link_libraries(MyLib PUBLIC tbb)

#libraries linked to MyLib target like tbb will used inside test target because it linked with MyLib and MyLib is public link with tbb

add_executable(test main.cpp ${res_files})
find_package(Qt6 REQUIRED COMPONENTS Core)
target_link_libraries(test PRIVATE Qt6::Core)
target_link_libraries(test  PUBLIC MyLib)

Mastering CMake (1)

Command Lines

Generating a project buildsystem

cmake [<options>] -S <path-to-source> -B <path-to-build>
cmake [<options>] <path-to-source>
cmake [<options>] <path-to-existing-build>
#Build in the ./build directory, and use a source from the current directory:
cmake -B build
#Build in the current directory, but take the source from one directory up (note that -S is optional):
cmake -S ..

choose generator
This can be overridden by the CMAKE_GENERATOR environment variable or by specifying the generator directly on the command line

cmake -G <generator-name> <path-to-source>

We can provide a path to the CMake script, which (only) contains a list of set() commands to specify variables that will be used to initialize an empty build tree.

cmake -D <var>[:<type>]=<value> <path-to-source>
cmake -S . -B build -D CMAKE_BUILD_TYPE=Release

to build project

cmake --build <dir>

Options for parallel builds

cmake --build <dir> --parallel [<number-of-jobs>]
cmake --build <dir> -j [<number-of-jobs>]

CMake Language

 the execution begins from the root file of the source tree (CMakeLists.txt) or a .cmake script file that was passed as an argument to cmake

Comments

# single-line comments start with a hash sign "#"
# they can be placed on an empty line
message("Hi"); # or after a command like here.
#[=[ 
bracket comment
  #[[
    nested bracket comment
  #]]
#]=]

Command invocations

command(argument1 "argument2" argument3) # comment
#[[ multiline comment ]] 

double qouted

message(${OpenCL_LIBRARY})
message("1. escape sequence: \" \n in a quoted argument")
message("2. multi...
  line")
message("3. and a variable reference: ${CMAKE_VERSION}")

Working with variables

1- Variable names are case-sensitive and can include almost any character.
2-All variables are stored internally as strings, even if some commands can interpret them as values of other data types (even lists!).
3-The basic variable manipulation commands are set() and unset(), but there are other commands that can affect variables, such as string() and list().

set(MyString1 "Text1")
set([[My String2]] "Text2")
set("My String 3" "Text3")
message(${MyString1})
message(${My\ String2})
message(${My\ String\ 3})
  • If the following reference is encountered – ${MyOuter${MyInner}} – CMake will try to evaluate MyInner first, rather than searching for a variable named MyOuter${MyInner}.
  • If the MyInner variable is successfully expanded, CMake will repeat the expansion process until no further expansion is possible.
  • The ${} syntax is used to reference normal or cache variables.
  • The $ENV{} syntax is used to reference environment variables.
  • The $CACHE{} syntax is used to reference cache variables.
set(ENV{CXX} "clang++")
cmake_minimum_required(VERSION 3.20.0)
project(Environment)
message("generated with " $ENV{myenv})

#!/bin/bash
export myenv=first
echo myenv is now $myenv
cmake -B build .
cd build
export myenv=second
echo myenv is now $myenv
cmake --build .
$ ./build.sh | grep -v "\-\-"
myenv is now first
generated with first
myenv is now second
Scanning dependencies of target EchoEnv
myenv in build is first
Built target EchoEnv

scope of variables

function(Inner)
  message("  > Inner: ${V}")
  set(V 3)
  message("  < Inner: ${V}")
endfunction()
function(Outer)
  message(" > Outer: ${V}")
  set(V 2)
  Inner()
  message(" < Outer: ${V}")
endfunction()
set(V 1)
message("> Global: ${V}")
Outer()
message("< Global: ${V}")
> Global: 1
 > Outer: 1
  > Inner: 2
  < Inner: 3
 < Outer: 2
< Global: 1
set(V 3 PARENT_SCOPE)
> Global: 1
 > Outer: 1
  > Inner: 2
  < Inner: 2
 < Outer: 3
< Global: 1

Lists

set(myList 1 2 3 4)
set(myList "a;list;of;five;elements")
set(myList a list "of;five;elements")

message("the list is:" ${myList}) 

The message() command will receive here six arguments: “the list is:“, “a“, “list“, “of“, “five“, “elements“.

list(LENGTH <list> <out-var>)
list(GET <list> <element index> [<index> ...] <out-var>)
list(JOIN <list> <glue> <out-var>)
list(SUBLIST <list> <begin> <length> <out-var>)
list(FIND <list> <value> <out-var>)
list(APPEND <list> [<element>...])
list(FILTER <list> {INCLUDE | EXCLUDE} REGEX <regex>)
list(INSERT <list> <index> [<element>...])
list(POP_BACK <list> [<out-var>...])
list(POP_FRONT <list> [<out-var>...])
list(PREPEND <list> [<element>...])
list(REMOVE_ITEM <list> <value>...)
list(REMOVE_AT <list> <index>...)
list(REMOVE_DUPLICATES <list>)
list(TRANSFORM <list> <ACTION> [...])
list(REVERSE <list>)
list(SORT <list> [...])

Understanding control structures in CMake

if(<condition>)
  <commands>
elseif(<condition>) # optional block, can be repeated
  <commands>
else()              # optional block
  <commands>
endif()

The provided <condition> expression is evaluated according to a very simple syntax.

The same syntax is valid for if()elseif(), and while() commands.

The if() conditions support the NOTAND, and OR logical operators:

  • NOT <condition>
  • <condition> AND <condition>
  • <condition> OR <condition>
  • (<condition>) AND (<condition> OR (<condition>))
  • EQUALLESSLESS_EQUALGREATER, and GREATER_EQUAL
  • “A” STREQUAL “${B}”
  • <file1> IS_NEWER_THAN <file2>: Checks which file is newe
  •  <VARIABLE|STRING> IN_LIST <VARIABLE>
  • <VARIABLE|STRING> MATCHES <regex>
set(VAR1 FALSE)
set(VAR2 "VAR1")
if(${VAR2})
  • ONYYES, or TRUE
  • A non-zero number
  • OFFNOFALSENIGNORENOTFOUND
  • A string ending with -NOTFOUND
  • An empty string
  • Zero

Loops

while(<condition>)
  <commands>
endwhile()
foreach(<loop_variable> IN [LISTS <lists>] [ITEMS <items>])
set(MY_LIST 1 2 3) #set(MY_LIST "1;2;3")
foreach(VAR IN LISTS MY_LIST)
  message(${VAR})
endforeach()

Command definitions

  • macro() command works more like a find-and-replace instruction than an actual subroutine call such as function().
  • The function() command creates a separate scope for local variables, unlike the macro() command, which works in the variable scope of a caller.
macro(<name> [<argument>…])
  <commands>
endmacro()
function(<name> [<argument>…])
  <commands>
endfunction()
macro(MyMacro myVar)
  set(myVar "new value")
  message("argument: ${myVar}")
endmacro()
set(myVar "first value")
message("myVar is now: ${myVar}")
MyMacro("called value")
message("myVar is now: ${myVar}")

Useful commands

execute_process to run external command

execute_process(COMMAND echo "hello world")

execute_process(COMMAND <cmd1> [args1...]]
                [COMMAND <cmd2> [args2...] [...]]
                [WORKING_DIRECTORY <directory>]
                [TIMEOUT <seconds>]
                [RESULT_VARIABLE <variable>]
                [OUTPUT_VARIABLE <variable>]
                [ERROR_VARIABLE <variable>]
                [INPUT_FILE <file>]
                [OUTPUT_FILE <file>]
                [ERROR_FILE <file>]
                [OUTPUT_QUIET]
                [ERROR_QUIET]
                [OUTPUT_STRIP_TRAILING_WHITESPACE]
                [ERROR_STRIP_TRAILING_WHITESPACE])

file to read or write from or to file

file(READ <filename> <out-var> [...])
file({WRITE | APPEND} <filename> <content>...)
file(DOWNLOAD <url> [<file>] [...])
file(GLOB_RECURSE QMLS "${QML_FOLDER}/*.qml") #get files with format as QMLS list
file(COPY ${QMLS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/${QML_FOLDER}) #Copy files

include to add and execute other CMake file

include(<file|module> [OPTIONAL] [RESULT_VARIABLE <var>])

Debian based ssh

install ssh server

$ sudo apt install openssh-server
$ sudo systemctl enable ssh
$ sudo systemctl start ssh

configure firewall and enable port 22

$ sudo ufw allow ssh
$ sudo ufw enable
$ sudo ufw status

add putty key

Now, you need to paste the copied public key in the file ~/.ssh/authorized_keys on your server.


$ mkdir ~/.ssh
$ chmod 0700 ~/.ssh
$ touch ~/.ssh/authorized_keys
$ chmod 0644 ~/.ssh/authorized_keys
after that you can copy public key created by putty to authoized_keys file
$ sudo systemctl restart sshd

Conan C++ Package Manager

install conan

sudo apt install python3-pip
pip install conan

looking for library

conan search poco --remote=conancenter
poco/1.8.1
poco/1.9.3
poco/1.9.4

creating a conanfile.txt inside our project’s folder with the following content:

[requires]
 poco/1.9.4

 [generators]
 cmake

Next step: We are going to install the required dependencies and generate the information for the build system:

$ conan profile new default --detect  # Generates default profile detecting GCC and sets old ABI
$ conan profile update settings.compiler.libcxx=libstdc++11 default  # Sets libcxx to C++11 ABI
$ mkdir build && cd build  #create build folder of cmake binary
$ conan install ..           #install libraries
#or conan install .. --settings os="Linux" --settings compiler="gcc"

Now let’s create our build file. To inject the Conan information, include the generated conanbuildinfo.cmake file like this:

 cmake_minimum_required(VERSION 2.8.12)
 project(MD5Encrypter)

 add_definitions("-std=c++11")

 include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
 conan_basic_setup()

 add_executable(md5 md5.cpp)
 target_link_libraries(md5 ${CONAN_LIBS})

C Libraries

stdlib

double atof(const char *str)
Converts the string pointed to, by the argument str to a floating-point number (type double).
int atoi(const char *str)
Converts the string pointed to, by the argument str to an integer (type int).
long int atol(const char *str)
Converts the string pointed to, by the argument str to a long integer (type long int).
double strtod(const char *str, char **endptr)
Converts the string pointed to, by the argument str to a floating-point number (type double).
long int strtol(const char *str, char **endptr, int base)
Converts the string pointed to, by the argument str to a long integer (type long int).
unsigned long int strtoul(const char *str, char **endptr, int base)
Converts the string pointed to, by the argument str to an unsigned long integer (type unsigned long int).
void *calloc(size_t nitems, size_t size)
Allocates the requested memory and returns a pointer to it.
void free(void *ptr)
Deallocates the memory previously allocated by a call to calloc, malloc, or realloc.
void *malloc(size_t size)Allocates the requested memory and returns a pointer to it.
void *realloc(void *ptr, size_t size)
Attempts to resize the memory block pointed to by ptr that was previously allocated with a call to malloc or calloc.
void abort(void)
Causes an abnormal program termination.
int atexit(void (*func)(void))
Causes the specified function func to be called when the program terminates normally.
void exit(int status)
Causes the program to terminate normally.
char *getenv(const char *name)
Searches for the environment string pointed to by name and returns the associated value to the string.
int system(const char *string)
The command specified by string is passed to the host environment to be executed by the command processor.
void *bsearch(const void *key, const void *base, size_t nitems, size_t size, int (*compar)(const void *, const void *))
Performs a binary search.
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
Sorts an array.
int abs(int x)
Returns the absolute value of x.
div_t div(int numer, int denom)
Divides numer (numerator) by denom (denominator).
long int labs(long int x)
Returns the absolute value of x.
ldiv_t ldiv(long int numer, long int denom)
Divides numer (numerator) by denom (denominator).
int rand(void)
Returns a pseudo-random number in the range of 0 to RAND_MAX.
void srand(unsigned int seed)
This function seeds the random number generator used by the function rand.
int mblen(const char *str, size_t n)
Returns the length of a multibyte character pointed to by the argument str.
size_t mbstowcs(schar_t *pwcs, const char *str, size_t n)
Converts the string of multibyte characters pointed to by the argument str to the array pointed to by pwcs.
int mbtowc(whcar_t *pwc, const char *str, size_t n)
Examines the multibyte character pointed to by the argument str.
size_t wcstombs(char *str, const wchar_t *pwcs, size_t n)
Converts the codes stored in the array pwcs to multibyte characters and stores them in the string str.
int wctomb(char *str, wchar_t wchar)
Examines the code which corresponds to a multibyte character given by the argument wchar.
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int main () {
   int n=4;//number of elements
   int *a;
   a = (int*)calloc(n, sizeof(int));

   char *str;
   str = (char *) malloc(15);
   strcpy(str, "tutorialspoint");
   str = (char *) realloc(str, 25);
   strcat(str, ".com");//append string

   free(str);
   free( a );
   
   return(0);
}

TypeScript

install and config with npm

npm install typescript -D -g
npm link typescript
npm install -D ts-node
npm install -P @types/node

add scripts to package.json

"scripts": {
    "start": "node --inspect=5858 -r ts-node/register ./tutorial.ts",
    "build": "tsc  && copyfiles -U 1 ./**/*.env ./dist"
  }

build script convert all ts files to js and move it to ./dist folder

copyfiles is a tool to help you to move files to destination folder

npm install -D copyfiles

tsconfig.json file  in a directory indicates that the directory is the root of a TypeScript project. The tsconfig.json file specifies the root files and the compiler options required to compile the project.

{
    "compilerOptions": {
        "module": "commonjs",
        "moduleResolution": "node",
        "pretty": true,
        "sourceMap": true,
        "target": "es6",
        "outDir": "./dist",
        "baseUrl": ".",
    },
    "include": [
        "./**/*"
    ],
    "exclude": [
        "node_modules",
        ".vscode"
    ]
}

so once you call tsc command it will build all files specified by tsconfig.json to target

let var1:string|number=44;
let var2:object={};
let var3:any;
let var4:void;//null or undefined
//===============================
let strArr:string[];//accept only array of string
let strArr2:[string,number];//tuple string,number
let strArr3:Array<string>;
//========================
let any:any;//take any value
let fun:(a: string) => void;//function type

TypeScript interface

interface Options {
  color: string;
  volume: number;
}
let options = {} as Options;
options.color = "red";
options.volume = 11;
interface User {
  name: string;
  id: number;
}
class UserAccount {
  name: string;
  id: number;
  constructor(name: string, id: number) {
    this.name = name;
    this.id = id;
  }
}
const user: User = new UserAccount("Murphy", 1);
interface  A{
    val1:string;
    val2:number;
}
class AA{
    val1:string;
    val2:number;
    val3:string;
    constructor(name:string,age:number) {
        this.val1=name;
        this.val2=age;
        this.val3=name+age;
    }
}
let val:A=new AA("hello",12);
console.log(val);

classes and abstract classes

interface IPerson {
    name: string;
    display():void;
}

interface IEmployee {
    empCode: number;
}

class Employee implements IPerson, IEmployee {
    empCode: number;
    name: string;
    
    constructor(empcode: number, name:string) {
        this.empCode = empcode;
        this.name = name;
    }
    
    display(): void {
        console.log("Name = " + this.name +  ", Employee Code = " + this.empCode);
    }
}

let per:IPerson = new Employee(100, "Bill");
per.display(); // Name = Bill, Employee Code = 100

let emp:IEmployee = new Employee(100, "Bill");
emp.display(); //Compiler Error: Property 'display' does not exist on type 'IEmployee'
class Car {
    name: string;
        
    constructor(name: string) {
        this.name = name;
    }
    
    run(speed:number = 0) {
        console.log("A " + this.name + " is moving at " + speed + " mph!");
    }
}

class Mercedes extends Car {
    
    constructor(name: string) {
        super(name);
    }
    
    run(speed = 150) {
        console.log('A Mercedes started')
        super.run(speed);
    }
}

class Honda extends Car {
    
    constructor(name: string) {
        super(name);
    }
    
    run(speed = 100) {
        console.log('A Honda started')
        super.run(speed);
    }
}

let mercObj = new Mercedes("Mercedes-Benz GLA");
let hondaObj = new Honda("Honda City")

mercObj.run();  // A Mercedes started A Mercedes-Benz GLA is moving at 150 mph!
hondaObj.run(); // A Honda started A Honda City is moving at 100 mph!
class StaticMem {  
   static num:number; 
   
   static disp():void { 
      console.log("The value of num is"+ StaticMem.num) 
   } 
} 

StaticMem.num = 12     // initialize the static variable 
StaticMem.disp()      // invoke the static method
abstract class Person {
    abstract name: string;

    display(): void{
        console.log(this.name);
    }
}

class Employee extends Person { 
    name: string;
    empCode: number;
    
    constructor(name: string, code: number) { 
        super(); // must call super()
        
        this.empCode = code;
        this.name = name;
    }
}

let emp: Person = new Employee("James", 100);
emp.display(); //James
class StaticMem {  
   static num:number; 
   
   static disp():void { 
      console.log("The value of num is"+ StaticMem.num) 
   } 
} 

StaticMem.num = 12     // initialize the static variable 
StaticMem.disp()      // invoke the static method

namespace

namespace SomeNameSpaceName { 
   export interface ISomeInterfaceName {      }  
   export class SomeClassName {      }  
} 
SomeNameSpaceName.SomeClassName;

as keyword

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

type aliases

type func=(a: string) => void;
type Point = {
  x: number;
  y: number;
};
 
// Exactly the same as the earlier example
function printCoord(pt: Point) {
  console.log("The coordinate's x value is " + pt.x);
  console.log("The coordinate's y value is " + pt.y);
}
 
printCoord({ x: 100, y: 100 });