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.
- Note: CMake may not be the best building system or not have the best IDEs. Now, it is supported by Microsft's Visual Studio and JetBrains' Clion IDE and others.
- See:
- 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
- Building system used by Microsft Visual Studio IDE. The configuration files, *.vcxproj and *.sln of Visual Studio Solution are both building systems scripts and project configuration files.
- Features:
- Most used building system on MS-Windows for C# and C++ projects.
- Some Windows frameworks such as MFC - Microsft Foundation and ATL - Active Template Library only support Visual Studio Solutions.
- Problems:
- Only supported by Visual Studio IDE and Visual Studio Code Editor.
- Only supported on Windows.
- Lots of cryptic XML files, not human-readable and not intended to be edited directly.
- The code from Visual Solution files cannot be reused. Any changes requires several clicks and browsing the IDE, thus not scalable.
- Hard to use external libraries dependencies.
- Hard to build from command line, require batch script hacks.
- Note:
- Visual Studio IDE, now has CMake support.
- MSBuild engine now is open source available at https://github.com/microsoft/msbuild
- See:
- 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:
- Gnome Builder
- KDevelop
- Eclipse CDT (Experimental support)
- Meson CMake Wrapper => For IDEs that support CMake (Visual Studio, CLion, QTCreator, KDevelop)
- Problems:
- Less IDE support and adoption than CMake and harder to use with IDEs.
- Features:
- 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
- Disadvantages:
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
- Make (Software) - Wikipedia
- Tutorial on writing makefiles
- Practical Makefiles, by example - John Tsiombikas
- Creating a Basic Makefile - Flex Technologies
- Debugging make -Tips and tricks to get make working for you, not against you - Peter Seebach
- What’s Wrong With GNU make?
- How can I configure my makefile for debug and release builds?
Makefiles for Latex, Tex compilation
- latex and Makefiles
- Makefiles for Latex
- A simple Makefile for LaTeX projects.
- Continuous integration and automatic deployment of LaTeX files
Makefiles for general automation
- Reproducibility with Make
- GNU Make for Reproducible Data Analysis
- Using Make for reproducible scientific analysis
- Reproducible bioinformatics pipelines using Make
- Reproducible Data Processing: Make + Docker
- Makefiles for R/LaTex Projects
- Makefiles in Python Projects
- Makefiles with Python
- Makefile for Python projects - GIST
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:
- GNU Emacs
- GNU Glibc (GNU C Runtime Library used in Linux)
- GNU GCC Compiler
- NetworkManager (Linux network manager daemon)
- Htop tool - htop is an interactive text-mode process viewer.
- Nhttp2 - (Implementation of the Hypertext Transfer Protocol version 2 in C.)
- LibFFI
- LibSodium
- 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
- Eclipse IDE
- NetBeans
- QTCreator
- 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.
- configure.ac for autoconf - written in a language similar to
Bash (Bourne Shell Script) which is processed by M4 macro
processor.
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
3.4 Further Reading
- How To Use Autotools
- GNU Autotools Book - https://www.star.bnl.gov/~liuzx/autobook.html
- Embedded Linux Conference 2016 - GNU Autotools Tutorial
- Chapter 1: A brief introduction to the GNU Autotools
- linux - How do I create a configure script? - Stack Overflow
- Autotools — Fedora Developer Portal
- C/C++ Project Built with GNU Build System (A.K.A. GNU Autotools): NetBeans vs. Eclipse CDT
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
4.0.3 Further Reading
Official Documentation
- QMake Manual - QT Company
- QMake - Getting Started - QT Company
- QMake Running
- QMake Variables
General
- Configuring QMake
- KDAB - Clang Tidy, part 2: Integrate qmake and other build systems using Bear
- KDAB - Qt for Android better than ever before
- QMake for neophytes
- SUBDIRS for Fun and Profit
- C++ GUI Programming with Qt4: Building Qt Applications
- Building a Linux system for the STM32MP1: setting up a Qt5 application development environment
Questions: