CPP / C++ - Building Systems and Build Automation

Table of Contents

1 Building Systems

1.1 Building System

Some building systems:

  • CMake Meta building system which can generate project files and building scripts for platform-preferred building systems. For instance, it can generate Makefile for U*nix-like system, Ninja build scripts, Visual Studio solutions and so on.
  • GNU Autotool (GNU Building System) - One of the most used building systems in open source projects for Unix-like operating systems, specially Linux.
    • Projects using GNU autotool have the files: configure.ac and Makefine.am. They often have the instruction for installing as: ./configure && make && make install.
    • Projects using Autotools: Emacs editor; GIT version control system; GNU Core utils; GIMP image manipulation app.
    • Problems:
      • Brittle and poorly designed.
      • Hard to read
      • Hard to use and complicated
      • Requires many configuration files.
      • Does not work well on Windows
      • Not famililar for Windows users.
  • MSBuild / Visual Studio Solutions
  • Meson - "Meson is an open source build system meant to be both extremely fast, and, even more importantly, as user friendly as possible. The main design point of Meson is that every moment a developer spends writing or debugging build definitions is a second wasted. So is every second spent waiting for the build system to actually start compiling code."
    • Features:
      • multiplatform support for Linux, macOS, Windows, GCC, Clang, Visual Studio and others
      • supported languages include C, C++, D, Fortran, Java, Rust
      • built-in multiplatform dependency provider that works together with distro packages
    • Side notes:
      • Can generate Visual Studio Projects
      • Uses Ninja building system as backend.
      • Support CMake dependencies
      • Supports Conan package manager
    • Projects using Meson:
    • IDE Support:
    • Problems:
      • Less IDE support and adoption than CMake and harder to use with IDEs.
  • QMake - QT Framework Building Systems
    • Problem: Brittle and only supported by QTCreator IDE.
    • Note: QT Now supports CMake
  • KBuild - Linux Kernel's building system. => Building system used for building Linux kernel image and kernel modules (device drivers).
  • build2 - "build2 is an open source (MIT), cross-platform build toolchain for developing and packaging C++ code. It is a hierarchy of tools that includes the build system, package dependency manager (for package consumption), and project dependency manager (for project development)."
  • xmake - "xmake is a cross-platform build utility based on lua. The project focuses on making development and building easier and provides many features (.e.g package, install, plugin, macro, action, option, task …), so that any developer can quickly pick it up and enjoy the productivity boost when developing and building project. "
    • Github: https://github.com/tboox/xmake
    • Advatange:
      • Lua-like syntax
      • Less verbose than CMake
      • Run lua scripts
      • Generation of projects, for instance Visual studio solutions.
  • Bazel (Building system created by Google for C and C++.)
    • "Bazel is an open-source build and test tool similar to Make, Maven, and Gradle. It uses a human-readable, high-level build language. Bazel supports projects in multiple languages and builds outputs for multiple platforms. Bazel supports large codebases across multiple repositories, and large numbers of users."
    • Note: Written in Java.
  • Make (GNU Make, BSD Make and NMake) - Simple and quick building system more used on Unix-like operating systems.
    • Disadvantages:
      • Fragmentation - There are many variants (GNU Make, BSD Make and NMake - Microsft's Make), although GNU Make is the most used one.
      • Brittle
      • Hard to modify
      • Hard to add dependencies

2 Make and GNU Make - Makefiles

2.0.1 Overview

Despite being one of the oldest building systems for C and C++, dating back to 1976, Make is still widely used on Unix-like operating systems, such as Linux, BSD-variants, MacOSX and so on. Make is not only useful for C or C++, the tool can also be used with any other programming language or for any type of building automation such as compiling Latex (Tex) files.

Make Variants (Implementations):

  • GNU Make (Most used)
  • BSD Make
  • NMake - Microsft's Make

Note:

  • This text assumes that the Make used is GNU Make

Advantages:

  • Easier usage
  • Available on most Unix-like operating systems
  • Can be used for automating any type of building.

Drawbacks:

  • Makefiles uses tabs instead of spaces which makes it brittle and easier to break if the editor settings are not appropriate.
  • Fragmentation: there is no a single Make tool, actually there are many incompatible implementations, namely: BSD Make; GNU Make, mostly used on Linux; Microsft's Make or NMake.
  • Hard to use on Windows, Make requires many helper executables such as echo, cp, ls, rm, …
  • Poor IDE support
  • No scalable for large projects
  • Hard to change
  • Hard to add dependencies
  • Hard to understand and modify

Command Line Summary

Make Command Description
$ make –version Show version
$ make –help Show help
   
$ make Run the file 'Makefile' in the current directory, executing the 'all' target.
$ make CC=clang CXX=clang++ Target 'all' for current Makefile, but sets C compiler to clang and C++ one to clang++
$ make <TARGET> Run the target <TARGET>
$ make <TARGET> VAR1=VAL1 VAR2=VAL2 Run some target named <TARGET> setting variable VAR1 to VAL1, VAR2 to VAL2.
$ make clean Run the target 'clean' for the Makefile in current directory.
$ make install Run the target 'install' for the current Makefile
$ make help Run the target 'help' for the current Makefile.
   
$ make -C /path/dir Run the file 'Makefile' in the directory /path/dir, executes 'all' target.
$ make -C /path/dir <TARGET> Executes target <TARGET> of Makefile in /path/dir
$ make -f Other.make Run the Makefile named 'Other.make' in current directory
$ make -f Other.make <TARGET> Run the target <TARGET> of Makefile named 'Other.make' in current directory.
$ make -C /path/dir -f Other.make Run the Makefile named Other.make' in directory /path/dir, executes 'all' target
$ make -C /path/dir -f Other.make <TARGET> Run the Makefile named Other.make' in directory /path/dir, executes 'all' target
   

Common Make variables Summary

Variable Description
CC C compiler, most common value is 'gcc'
CXX C++ Compiler, most common value is 'g++'
LD Linker
   
CFLAGS C compilation flags, i.e: -Wall -Wextra -g …
CXXFLAGS C++ compilation flags
   
   

Target Shortcuts summary

Variable Description
Target  
$@ target file name
$(@D) target path (directory only)
$(@F) target file name without directory
   
Dependencies  
$< name of first dependency
$* name of first dependency without extension
$^ name of all dependencies of the target
$(<D) path of first dependencies
$(<F) paths of all dependencies
   

Makefile Variable Tricks Summary

Trick Expansion Description
SRC := $(wildcard src/*.cpp) SRC := src/fil1.cpp src/file2.cpp … Gets all files with *.cpp extension in ./src
packs := n0 n1 n2    
packs-p := $(addprefix –pack ,$(packs)) pack-p := –pack n1 –pack n2 –pack n3 Add prefix –pack to each n[i]
pwd := $(shell pwd) pwd := /home/archbox/project1 Expands to shell command output.
${HOME} /home/user/username Expands to environment variable
file := ${HOME}/file1.exe /home/user/username/file1.exe -
     

Multi line variable assignment

CFLAGS  = -DUSE_STDPERIPH_DRIVER
CFLAGS += -c -fmessage-length=0 -g3 -gdwarf-2 -O0 -Wall -Wa,-adhlns="$@.lst"
CFLAGS += -mthumb -mcpu=cortex-m4
CFLAGS += -MMD -MP -MF"$@.d" -MT"$@.d"
CFLAGS += -Iinc -Iinc/cmsis -Iinc/peripherals -Iinc/stm32f4xx

2.0.2 Project Example

File: Makefile

   #---------------------------------------------------#
   #     Compilation and Linker Settings               #
   #---------------------------------------------------#

CXX = g++   # C++ Compiler
CC  = gcc   # C Compiler

CCFLAGS  =                          # C compiler flags
CXXFLAGS = -std=c++1z -pedantic -Wall -Wextra # C++ Compiler flags

LDFLAGS   =   # C Linker Flags
LDXXFLAGS =   # C++ Linker flags

# DEBUG = 1 => Enables debug building
# DEBUG = 0 => Enable release building
DEBUG ?= 0
ifeq ($(DEBUG), 1)
    # (-g)  => Enable debug symbols
    # (-O0) => Disable optimizations
    # -DNDEBUG => Enable assertions
    CXXFLAGS  += -g -O0  -DNDEBUG
else
     # Release mode =>> Enable optimization
     CXXFLAGS += -O3
     # Enable Link-time optimiztion
     LDXXFLAGS += # -flto -ffunction-sections -fdata-sections
endif

APP2_OBJS = app2.o stats.o

   #---------------------------------------------------#
   #     Makefile Rules                                #
   #---------------------------------------------------#

# Main rule executed when use runs '$ make' without
# any argument.
#---------------------------------------------------
all: app1.bin app2.bin

# This rule builds the executable 'app1.bin'
#----------------------------------------------------
#  $^ => Refers to all dependencies (app1.o, app1.o)
#  $@ => Refers to the target name, 'app1.bin'
app1.bin : app1.o stats.o
        @echo -e "\n [INFO] Building executable 'app1.bin'"
        $(CXX) -o $@ $^ $(LDXXFLAGS)

# This rule builds the executable 'app2.bin'
#----------------------------------------------------
#  $^ => Refers to all dependencies (app1.o, app1.o)
#  $@ => Refers to the target name, 'app1.bin'
app2.bin : $(APP2_OBJS)
        @echo -e "\n [INFO] Building executable 'app2.bin'"
        $(CXX) -o $@ $^ $(LDXXFLAGS)

# Compiles all *.cpp files to *.o (object-code files) which
# are inputs to the linker.
#
#--------------------------------------------------
#  $< => Name of dependency (on right-hand size, example: someFile.cpp)
#  $@ => Name of target (on the left-hand side)
#
%.o : %.cpp
        @echo -e "\n [INFO] Building object code: $<"
        $(CXX) -c $< -o $@  $(CXXFLAGS)

# This rule runs the executable app1.bin, the dependency is 'app1.bin'
#------------------------------------------------------------
app1_run: app1.bin
        ./app1.bin

# This rule deletes all object-codes (*.o, *.bin, *.so, *.dll, *.exe ...)
#-------------------------------------------------------
clean:
        @echo -e "\n [INFO] Cleaning compilation output."
        rm -rf -v *.o *.bin

install: app1.bin app2.bin
        @echo -e "\n [INFO] Installing application to: $(INSTALL_PATH)"
        mkdir -p $(INSTALL_PATH)
        cp -rv $^ $(INSTALL_PATH)

File: stats.hpp

#include <iostream>
#ifndef _STATS_HPP_
#define _STATS_HPP_

class Stats
{
public:
        Stats();
        ~Stats();
        Stats(const Stats&) = delete;
        Stats& operator=(const Stats&) = delete;
        void   add(double x);
        void   clear();
        double mean() const;
        double sum()  const;
        size_t count() const;   
private:
        struct pimpl;
        pimpl* m_pimpl;
};

#endif 

File: stats.cpp

#include <iostream>
#include <cmath>
#include <algorithm> 
#include <vector>
#include <numeric>

#include <stats.hpp>

struct Stats::pimpl {
   std::vector<double> dataset = {};
};

Stats::Stats(): m_pimpl(new Stats::pimpl)
{ }

Stats::~Stats() {
    std::puts(" [INFO] ~Stats() destructor called. Ok. ");
    delete m_pimpl;
    m_pimpl = nullptr;
}

void Stats::add(double x) 
{
    m_pimpl->dataset.push_back(x);
}

void Stats::clear()
{
   m_pimpl->dataset.clear();
}

double Stats::sum() const
{
    return std::accumulate( m_pimpl->dataset.begin()
                          , m_pimpl->dataset.end(), 0.0);
}

double Stats::mean() const 
{
    return this->sum() / m_pimpl->dataset.size();
}

size_t Stats::count() const {
    return m_pimpl->dataset.size();
}

File: app1.cpp

#include <iostream>
#include <stats.hpp> 

int main()
{
      std::cout << " [TRACE] Running application 'app1' - OK" << "\n";

      Stats st;
      st.add(10.0); st.add(20.6354); st.add(-5.62); st.add(8.623);
      st.add(20.0); st.add(20.6354); st.add(-5.62); st.add(8.623);

      std::cout << "  => st.count() = " << st.count() << "\n";
      std::cout << "  =>   st.sum() = " << st.sum() << "\n";
      std::cout << "  =>  st.mean() = " << st.mean() << "\n";

      return 0;
}

File: app2.cpp

#include <iostream>
#include <string>

#include "stats.hpp"

int main(int argc, char** argv)
{
     std::cout << " [INFO] Running application: 'app2' - OK" << "\n";

     if(argc < 2){
         return EXIT_FAILURE;
     }

     Stats st;

     for(int i = 1; i < argc; i++)
        try {
           st.add( std::stod(argv[i]) );
        } catch(std::invalid_argument const& ex)
        {
           std::cerr << " Error: invalid input <" << argv[i] << std::endl;
           return EXIT_FAILURE; 
        }

     std::cout << "  => st.count() = " << st.count() << "\n";
     std::cout << "  =>   st.sum() = " << st.sum() << "\n";
     std::cout << "  =>  st.mean() = " << st.mean() << "\n";    

     return EXIT_SUCCESS;
}

Run target 'all' (Release mode)

  • $ make => Runs rule 'all'
$ make

 [INFO] Building object code: app1.cpp
g++    -c app1.cpp -o app1.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building object code: stats.cpp
g++    -c stats.cpp -o stats.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building executable 'app1.bin'
g++    -o app1.bin app1.o stats.o  

 [INFO] Building object code: app2.cpp
g++    -c app2.cpp -o app2.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building executable 'app2.bin'
g++    -o app2.bin app2.o stats.o  

Run target 'all' (Debug Mode)

Run target 'all' which builds target 'app1.bin', 'app2.bin'

$ make DEBUG=1

 [INFO] Building object code: app1.cpp
g++    -c app1.cpp -o app1.o  -std=c++1z -pedantic -Wall -Wextra  -g -O0  -DNDEBUG

 [INFO] Building object code: stats.cpp
g++    -c stats.cpp -o stats.o  -std=c++1z -pedantic -Wall -Wextra  -g -O0  -DNDEBUG

 [INFO] Building executable 'app1.bin'
g++    -o app1.bin app1.o stats.o 

 [INFO] Building object code: app2.cpp
g++    -c app2.cpp -o app2.o  -std=c++1z -pedantic -Wall -Wextra  -g -O0  -DNDEBUG

 [INFO] Building executable 'app2.bin'
g++    -o app2.bin app2.o stats.o 

Debug application 'app1.bin'

 $ gdb --silent app1.bin 
 Printing of typedefs defined in a class in on
 Reading symbols from app1.bin...done.

 (gdb) list
 1      #include <iostream>
 2      
 3      #include <stats.hpp> 
 ... ... ... ... 

 # Set breaking point at main() 
 (gdb) b main

 # Run program app1.bin 
 (gdb) r

 gdb) n
  [TRACE] Running application 'app1' - OK
 9              Stats st;

 (gdb) p st
 $1 = {
   m_pimpl = 0x616280
 }

 ... ...    ... ...    ... ...    ... ... 

(gdb) p st->m_pimpl->dataset
$5 = std::vector of length 8, capacity 8 = {10, 20.635400000000001, -5.6200000000000001, 
  8.6229999999999993, 20, 20.635400000000001, -5.6200000000000001, 8.6229999999999993}
(gdb) 

Run target 'clean'

Clean all compilation files:

$ make clean

 [INFO] Cleaning compilation output.
rm -rf -v *.o *.bin
removed 'app1.o'
removed 'app2.o'
removed 'stats.o'
removed 'app1.bin'
removed 'app2.bin'

Run target 'app1.bin'

$ make app1.bin

 [INFO] Building object code: app1.cpp
g++    -c app1.cpp -o app1.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building object code: stats.cpp
g++    -c stats.cpp -o stats.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building executable 'app1.bin'
g++    -o app1.bin app1.o stats.o  

Run target 'app2.bin'

$ make app2.bin

 [INFO] Building object code: app2.cpp
g++    -c app2.cpp -o app2.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building executable 'app2.bin'
g++    -o app2.bin app2.o stats.o  

Run target 'app1_run'

The target app1_run runs the executable 'app1.bin'. It is only compiled if not built yet.

$ make app1_run

 [INFO] Building object code: app1.cpp
g++    -c app1.cpp -o app1.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building object code: stats.cpp
g++    -c stats.cpp -o stats.o  -std=c++1z -pedantic -Wall -Wextra  -O3

 [INFO] Building executable 'app1.bin'
g++    -o app1.bin app1.o stats.o  
./app1.bin
 [TRACE] Running application 'app1' - OK
  => st.count() = 8
  =>   st.sum() = 77.2768
  =>  st.mean() = 9.6596
 [INFO] ~Stats() destructor called. Ok. 

Run target again:

$ make app1_run
./app1.bin
 [TRACE] Running application 'app1' - OK
  => st.count() = 8
  =>   st.sum() = 77.2768
  =>  st.mean() = 9.6596
 [INFO] ~Stats() destructor called. Ok. 

Run target 'install'

Run target 'make install'

$ make install INSTALL_PATH=/tmp/bin DEBUG=1

 [INFO] Building object code: app1.cpp
g++    -c app1.cpp -o app1.o  -std=c++1z -pedantic -Wall -Wextra  -g -O0  -DNDEBUG

  ... ... ...   ... ... ...   ... ... ...   ... ... ...   ... ... ... 

 [INFO] Installing application to: /tmp/bin
mkdir -p /tmp/bin
cp -rv app1.bin app2.bin /tmp/bin
'app1.bin' -> '/tmp/bin/app1.bin'
'app2.bin' -> '/tmp/bin/app2.bin'

Test applications/executables:

$ /tmp/bin/app2.bin 10.354 -9.243  100.523 5.6154
 [INFO] Running application: 'app2' - OK
  => st.count() = 4
  =>   st.sum() = 107.249
  =>  st.mean() = 26.8123
 [INFO] ~Stats() destructor called. Ok. 

2.0.3 Reading

Documentation

Useful Reading

Makefiles for Latex, Tex compilation

Makefiles for general automation

3 GNU Autotools Building System

3.1 Overview

  • Autotools
    • =>> GNU Building System or umbrela name for autoconf, automake and libtool. GNU autotools is used by many open-source Linux projects and embedded Linux.
  • Some Projects using GNU Autotools:
  • Used mostly in Unix-like or POSIX-like operating systems, specially Linux.
  • Motivation:
    • Deal with legacy systems or software
    • Learn about alternative building systems
    • Maintenance of existing open source projects
    • Possible porting GNU autotools to CMake
    • Embedded Linux
  • Supported IDEs - Integrated Development Environments
  • Availalble for:
    • Linux, MacOSX, FreeBSD, OpenBSD, NetBSD and so on.
  • Documentation:
  • Supported Programming Languages
    • C
    • C++
    • Objective C
    • Fortran
    • Fortran77
    • Erlang
  • Terminal Documentation:
    • $ info automake
    • $ info autoconf
    • $ info libtool
  • Common build configuration facilities
    • $ ./configure
    • $ make
    • $ make install => Install application.
    • $ … … ..

3.2 Reference Card

Installation

Install on Fedora Linux Distribution:

$ sudo dnf install autoconf automake

Install on Ubuntu or Debian Linux Distributions:

$ apt-get install autoconf automake 

GNU Make Command for GNU autotools

Make Command Description
$ make Build application
$ make -j4 Build applciation using 4 threads
   
$ make check Run tests
   
$ make clean Clean compilation files
$ make distclean Remove generated autools files.
   
$ make install Install application in the system to: /usr/loca/bin, /etc, …
$ make intallcheck Check installation
$ make uninstall Remove installation files
   
$ make dist Create source distribution tarball
$ make distcheck Similar to make dist, but checks if the compilation, the creates a tarball.
   

Most Configuration variables

Variable Description  
CC C compiler  
CXX C++ compuler  
     
CPPFLAGS C and C++ Preprocessor flags  
CFLAGS Flags of the C compiler  
CXXFLAGS Flags of the C++ compiler  
     
LDFLAGS Linker Flags  
LIBS Libraries passed to the linker  
     

Useful ./configure (Autotools) command line switches

Those are useful and recurring command line switches for building open source applications or libraries from source.

  • Show ./configure help
$ ./configure --help 
  • General ./configure usage example.
# ---- Example 1 --------------------#
# Install to /usr, /usr/bin, /usr/include, /usr/lib, /usr/lib64 on Linux 
$ ./configure  
$ make 
$ make install 

# ---- Example 2 --------------------#
$ ./configure  --with-feature1 --without-feature2 --eanable-option1 --enable-option2=no 
$ make 
$ make install 

# ---- Example 3 --------------------#
# Install to custom location (directory)
$ ./configure --prefix=/my/custom/location  --with-feature1 \
     --without-feature2 --eanable-option1 --enable-option2=no --disable-option3

$ make -j4 # Build using 4 threads (faster)
$ make install 

Autotools workflow for users

STEP 1: User run ./configure selecting build options;

# Install application in default directory
$ ./configure

# Install the application in another directory 
$ ./configure --prefix=/home/user/juliuscaesar/opt

STEP 2: Run (GNU) make for building.

$ make 

# Run GNY make with 4 threads 
$ make -j4 

# Run $ make and $ make install in a single step 
$ make -j4 && sudo make install 

STEP 3: Install.

$ make install 

# If permission is needed 
$ sudo make install 

Developer Worflow

STEP 1:

  • => Create files:
    • configure.ac for autoconf - written in a language similar to Bash (Bourne Shell Script) which is processed by M4 macro processor.
      • Note: It is necessary to take care with whitespaces in macro invocations.
    • Makefile.am for automake.

STEP 2:

  • => The developer runs autoreconf that generates the files ./configure and Makefile.in

STEP 3:

  • => Test the generated files by running:
    • $ ./configure && make && make install.

3.3 Example: Basic GNU autotools project

3.3.1 Project Files

File: main.cpp

#include <iostream>

int main()
{
  std::cout << "Hello world autotools" << std::endl;
  return 0;
}

File: configure.ac

#------- File: configure.ac ----------#
#-------------------------------------# 

# app => Is the file name without extensions
# 0.1 => Is the version 
AC_INIT( [app], [0.1], [maintener@email.com])

# Require at least autoconf version >= 2.68 
AC_PREREQ([2.68])  
AM_INIT_AUTOMAKE([-Wall -Wextra])

# ./configure creates a Makefile
AC_CONFIG_FILES([Makefile])

# Find and check C compiler 
AC_PROG_CC
# Find and check a C++ compiler 
AC_PROG_CXX 
AC_OUTPUT 

File: Makefile.am

# Contains all executable targets 
bin_PROGRAMS = app

# Sources for the executable targets 
app_SOURCES  = app.cpp 

File: clean.sh

  • Helper bash script used for cleaning current directory.
#!/usr/bin/env sh 
rm -v configure config.log config.status install-sh missing aclocal.m4 compile depcomp 
rm -vrf autom4te.cache
rm -v *.o *.bin *.so
rm *~ # Remove temporary files 

3.3.2 Building Steps

STEP 1: Performed by developer

  • Create additional metadata files needed by GNU autotools
$ touch README NEWS AUTHORS ChangeLog # Set metadata files                                                │

STEP 2: Performed by developer

  • Run atuoreconf for generating ./configure shell script and addition files.
  • Run: $ autoreconf -i -v
$ autoreconf -i -v 

autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal 
autoreconf: configure.ac: tracing
autoreconf: configure.ac: not using Libtool
autoreconf: running: /usr/bin/autoconf
autoreconf: configure.ac: not using Autoheader
autoreconf: running: automake --add-missing --copy --no-force
configure.ac:11: warning: unknown warning category 'extra'
configure.ac:17: installing './compile'
configure.ac:11: installing './install-sh'
configure.ac:11: installing './missing'
Makefile.am: installing './depcomp'
autoreconf: Leaving directory `.'

STEP 3: Performed by user

  • The following steps are performed by end-users or maintainers for installing the application in the system or in a local directory.
  • Generate the Makefile by running the configuration script.

Run: $ ./configure

$ ./configure 
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk

... ... ...   ... ... ...   ... ... ...   ... ... ... 
     # Suppress output for breviety purposes 
... ... ...   ... ... ...   ... ... ...   ... ... ... 

checking dependency style of g++... gcc3
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands

Run: $ ./configure CC=clang CXX=clang++ for using clang.

$ ./configure CC=clang CXX=clang++

STEP 4: Performed by user

  • Run the makefile (GNU make)

Run: $ make

$ make

g++ -DPACKAGE_NAME=\"app\" -DPACKAGE_TARNAME=\"app\" -DPACKAGE_VERSION=\"0.1\"  \
    -DPACKAGE_STRING=\"app\ 0.1\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\"   \
     -DPACKAGE=\"app\" -DVERSION=\"0.1\" -I.     -g -O2 -MT app.o -MD -MP -MF   \
    .deps/app.Tpo -c -o app.o app.cpp \
    mv -f .deps/app.Tpo .deps/app.Po \

 g++  -g -O2   -o app app.o  

Run: $ make install

$ make install
make[1]: Entering directory '/home/archbox/projects/autools1'
 /usr/bin/mkdir -p '/usr/local/bin'
  /usr/bin/install -c app '/usr/local/bin'
/usr/bin/install: cannot create regular file '/usr/local/bin/app': Permission denied
make[1]: *** [Makefile:313: install-binPROGRAMS] Error 1
make[1]: Leaving directory '/home/archbox/projects/autools1'
make: *** [Makefile:615: install-am] Error 2

Run: $ make dist => Create a tarball distribution.

$ make dist
make  dist-gzip am__post_remove_distdir='@:'
make[1]: Entering directory '/home/archbox/projects/autools1'
if test -d "app-0.1"; then find "app-0.1" -type d ! -perm -200 -exec chmod u+w {} ... ... 
test -d "app-0.1" || mkdir "app-0.1"

  .. ... ... ... ... ... ... 
rm -rf "app-0.1" || { sleep 5 && rm -rf "app-0.1"; }; else :; fi

Check the tarball file:

    $ tar -tzvf app-0.1.tar.gz 
drwxrwxr-x 1000/1000         0 2019-10-21 15:23 app-0.1/
-rw-rw-r-- 1000/1000     42147 2019-10-21 15:22 app-0.1/aclocal.m4
-rwxrwxr-x 1000/1000    154043 2019-10-21 15:22 app-0.1/configure
-rw-rw-r-- 1000/1000       546 2019-10-21 15:01 app-0.1/configure.ac
-rw-rw-r-- 1000/1000       101 2019-10-21 14:28 app-0.1/app.cpp
-rwxr-xr-x 1000/1000     14676 2019-10-21 15:22 app-0.1/install-sh
-rw-rw-r-- 1000/1000         0 2019-10-21 15:02 app-0.1/ChangeLog
-rwxr-xr-x 1000/1000      6872 2019-10-21 15:22 app-0.1/missing
-rwxr-xr-x 1000/1000     23566 2019-10-21 15:22 app-0.1/depcomp
-rw-rw-r-- 1000/1000         0 2019-10-21 15:02 app-0.1/AUTHORS
-rw-r--r-- 1000/1000     35147 2019-10-21 15:01 app-0.1/COPYING
-rw-r--r-- 1000/1000     15756 2019-10-21 15:01 app-0.1/INSTALL
-rwxr-xr-x 1000/1000      7381 2019-10-21 15:22 app-0.1/compile
-rw-rw-r-- 1000/1000       117 2019-10-21 14:52 app-0.1/Makefile.am
-rw-rw-r-- 1000/1000         0 2019-10-21 15:02 app-0.1/README
-rw-rw-r-- 1000/1000         0 2019-10-21 15:02 app-0.1/NEWS
-rw-rw-r-- 1000/1000     23477 2019-10-21 15:22 app-0.1/Makefile.in

STEP 5:

  • Test the application.
# Check executable file 
$ file app
app: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter 
/lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=29f5b104ac648c3286ed616ea4cf4007a6b51ae2, 
with debug_info, not stripped

# Run native executable 
$ ./app
Hello world autotools

Build and install the application into a new location

Run configure script.

# Build with clang and install at ~/opt/app-test 
$ ./configure CC=clang CXX=clang++ --prefix=$HOME/opt/app-test

Run make and make install

$ make install
make[1]: Entering directory '/home/archbox/projects/autools1'
 /usr/bin/mkdir -p '/home/archbox/opt/app-test/bin'
  /usr/bin/install -c app '/home/archbox/opt/app-test/bin'
make[1]: Nothing to be done for 'install-data-am'.
make[1]: Leaving directory '/home/archbox/projects/autools1'

Test installation:

$ tree /home/archbox/opt/app-test
/home/archbox/opt/app-test
└── bin
    └── app

1 directory, 1 file

Run the application:

$ /home/archbox/opt/app-test/bin/app 
Hello world autotools

4 QMake Building System

4.0.1 Overview

QMake is a building system generator used by QT Framework and QT Creator IDE, similar to CMake, which can generate Makefiles and projects for Visual Studio IDE (MSBuild building system).

Features:

  • QT Framework now provides official support to CMake, but QMake is still used in many old projects and libraries.

Benefits:

  • For now, QMake still provides better support fro cross compilation and for mobile platforms, such as Android or iOS.
  • Multi-ABI cross-compilation support.

Problems:

  • Not supported by other IDEs than QT Creator. So, it is not supported by Eclipse, Netbeans or Visual Studio.
  • Brittle building system => It does not support multiple targets or generating multiple executables in the same building script.
  • Hard to use with third-party libraries or libraries using CMake or other building systems.

4.0.2 QMake project example

Project Structure:

$ cd /path/to/project-root/dir
$ tree .
.
├── app1
│   ├── app1.cpp
│   ├── app1.pro
│   ├── dataset.cpp
│   └── dataset.hpp
├── app2
│   ├── app2.cpp
│   └── app2.pro
├── apptest.pro

File: apptest.pro

TEMPLATE = subdirs
SUBDIRS = app1 app2

Target app1 - subproject

  • Target app1 is a console executable.

File: app1/app1.pro

TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundle
CONFIG -= qt

SOURCES += \
        dataset.cpp \
        app1.cpp

HEADERS += \
    dataset.hpp

File: app1/app1.cpp

#include <iostream>
#include "dataset.hpp"

int main()
{
    Dataset ds;
    ds.add_data(10.34);
    ds.add_data(100.23);
    ds.add_data(-100.0);

    std::cout << "Hello World!" << std::endl;
    std::cout << " ds[0] = " << ds.get_data(0) << std::endl;
    std::cout << " ds[1] = " << ds.get_data(0) << std::endl;
    std::cout << " ds[2] = " << ds.get_data(0) << std::endl;

    return 0;
}

File: app1/dataset.hpp

#ifndef DATASET_HPP
#define DATASET_HPP

#include <cstddef>

class Dataset{
    struct pimpl;
    pimpl* m_pimpl;
public:
    Dataset();
    ~Dataset();
    Dataset(Dataset cosnt&) = delete;
    Dataset& operator=(Dataset const&) = delete;
    void   add_data(double x);
    double get_data(size_t idx) const;
};

#endif // DATASET_HPP

File: app1/dataset.cpp

#include "dataset.hpp"

#include <memory>
#include <vector>
#include <iostream>

struct Dataset::pimpl
{
    std::vector<double> data;
};


Dataset::Dataset(): m_pimpl(new pimpl)
{
    std::cout << " [INFO] Constructor called. OK" << std::endl;
}

Dataset::~Dataset(){
    delete m_pimpl;
}

void Dataset::add_data(double x)
{
    m_pimpl->data.push_back(x);
}

double Dataset::get_data(size_t i) const
{
    return m_pimpl->data[i];
}

Target app2 - subproject

  • Target app2 is a user interface QT Widgets executable which file name is app2_linux_x64.bin

File: app2/app2.pro

QT       += core gui widgets
TEMPLATE  = app
CONFIG   += c++1z
# CONFIG  += console c++1z
# CONFIG  -= app_bundle

# Change
TARGET   = app2_linux_x64.bin
SOURCES += app2.cpp 

File: app2/app2.cpp

#include <iostream>

#include <QApplication>
#include <QtWidgets>

int main(int argc, char* argv[])
{
    std::cout << " [INFO] Starting Application 2 OK" << std::endl;

    QApplication app(argc, argv);

    QPushButton button("Click me");
    button.setVisible(true);

    QObject::connect(&button, &QPushButton::clicked,
                     []{
                         std::cout << " [INFO] I Was clicked OK" << std::endl;
                     });

    return app.exec();
}

Building and Running

STEP 1 - Create a ./build directory for separating the compilation output from the source code.

$ mkdir build && cd build

STEP 2 - Run QMake

$ qmake ../

$ ls
Makefile

STEP 3 - Run Makefile

$ make

$ tree .
.
├── app1
│   ├── app1
│   ├── app1.o
│   ├── dataset.o
│   └── Makefile
├── app2
│   ├── app2_linux_x64.bin
│   ├── app2.o
│   └── Makefile
└── Makefile

2 directories, 8 files

STEP 4 - Check executables

# Run executable app1 
#--------------------------------------
$ app1/app1 
 [INFO] Constructor called. OK
Hello World!
 ds[0] = 10.34
 ds[1] = 10.34
 ds[2] = 10.34

# Run executable app2 
#--------------------------------------
$ app2/app2_linux_x64.bin 
 [INFO] Starting Application 2 OK

Created: 2021-06-04 Fri 15:07

Validate