Under the hood of qmake

As we said earlier, qmake is the foundation of the Qt framework compilation system. In Qt Creator, when you click on the Build button, qmake is invoked. Let's study what qmake is exactly doing by calling it ourselves on the CLI (Command Line Interface).

Create a temporary directory where you will store the generated files. We are working on a Linux box, but this is transposable on any OS. We chose /tmp/sysinfo. Using the CLI, navigate to this new directory and execute the following command:

/path/to/qt/installation/5.7/gcc_64/bin/qmake -makefile -o Makefile /path/to/sysinfoproject/ch02-sysinfo.pro

This command will execute qmake in the -makefile mode to generate a Makefile based on your sysinfo.pro file. If you skim through the Makefile content, you will see many things we covered earlier in the .pro section. The link to Qt modules, headers of different modules, inclusion of the headers and sources files of your project, and so on.

Now, let's build this Makefile by simply typing the make command.

This command will generate the binary ch02-sysinfo (based on the TARGET value of the .pro file). If you look at the list of files now present in /tmp/sysinfo:

    $ ls -1
    ch02-sysinfo
    CpuWidget.o
    main.o
    MainWindow.o
    Makefile
    MemoryWidget.o
    moc_CpuWidget.cpp
    moc_CpuWidget.o
    moc_MainWindow.cpp
    moc_MainWindow.o
    moc_MemoryWidget.cpp
    moc_MemoryWidget.o
    moc_SysInfoWidget.cpp
    moc_SysInfoWidget.o
    SysInfoLinuxImpl.o
    SysInfo.o
    SysInfoWidget.o
    ui_MainWindow.h

Now this is very interesting, we find all our sources compiled in the usual .o extension (SysInfo.oSysInfoWidget.o, and so on) but there are also a lot of other files prefixed with moc_. Here lies another keystone of the Qt framework: the Meta Object Compiler.

Every time you use the signal/slot system, you have to include the macro Q_OBJECT in your header. Each time you emit a signal or receive one in a slot and you did not write any specific code to handle it, Qt took care of it. This is done by generating an intermediate implementation of your class (the moc_*.cpp file) containing everything Qt needs to properly handle your signals and slots.

A picture is worth a thousand words. Here is the complete compilation pipeline for a standard qmake project:

The blue boxes refer to commands and the wavy boxes are documents (sources or final binary). Let's walk through the steps:

  1. The qmake command is executed with the project .pro file. It generates a Makefile based on the project file.
  2. The make command is executed, which will call other commands to generate intermediate files.
  3. The uic command stands for User Interface Compiler. It takes all the .ui files (which are basically an XML description of your interface) and generates the corresponding ui_*.h header that you include in your own .cpp (in our ch02-sysinfo project, it is in MainWindow.cpp).
  4. The moc command takes every class containing the Q_OBJECT macro (paired with the superclass QObject) and generates the intermediate moc_*.cpp files, which include everything needed to make the signal/slot framework work.
  5. The g++ command is executed, compiling all your sources' files and intermediate moc files into .o files before finally linking everything in the binary ch02-sysinfo.
Note that if you add a Q_OBJECT macro after the creation of a class, sometimes the compiler will complain about your signals and slots. To fix this, simply run the  qmake command from  BuildRun qmake. You can now see that this stems from the fact that the Makefile has to be regenerated to include the generation of the new intermediate moc file.

Generally, source code generation is regarded as bad practice in the developer community. Qt has been criticized on this topic for a long time. We always fear that the machines does some kind of voodoo behind our back. Unfortunately, C++ does not offer any practical way of doing code introspection (namely reflection), and the signal and slots mechanism needs some kind of metadata about your class to resolve your signals and slots. This could have been done partly with the C++ template system, but this solution seemed to Qt to be much less readable, portable, usable, and robust. You also need an excellent compiler support for templates. This cannot be assumed in the wild world of C++ compilers.

The moc system is now fully mature. There are some very specific edge cases where it could bring trouble (some have reported problems in very specific situations with Visual Studio), but even so, we think that the gain of this feature largely outweighs the possibly encountered issues. The signal/slot system is a marvel to work with, and if you look at the beginnings of Qt, the system has been present from the very first releases. Adding the functor notation in Qt 5 (which gives a compile time check of the validity of your connect()) combined with C++11 lambas makes it a real delight.