The .pro file in depth
When you click on the Build button, what exactly is Qt Creator doing? How does Qt handle the compilation of the different platforms with a single .pro file? What does the Q_OBJECT macro imply exactly? We will dig into each of these questions in the following sections. Our example case will be the SysInfo application we just completed, and we will study what Qt is doing under the hood.
We can start this study by digging into the .pro file. It is the main entry point in compiling any Qt project. Basically, a .pro file is a qmake project file describing the sources and headers used by the project. It is a platform-agnostic definition of a Makefile. First, we can cover the different qmake keywords used in the ch02-sysinfo application:
#------------------------------------------------- # # Project created by QtCreator 2016-03-24T16:25:01 # #------------------------------------------------- QT += core gui charts CONFIG += C++14 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = ch02-sysinfo TEMPLATE = app
Each of these functions has specific roles:
- #: This is the prefix needed to comment on a line. Yes, we generated the project on 2016-03-24-crazy, huh?
- QT: This is a list of the Qt modules used in the project. In the platform-specific Makefile, each of the values will include the module headers and the corresponding library link.
- CONFIG: This is a list of configuration options for the project. Here, we configure the support of C++14 in the Makefile.
- TARGET: This is the name of the target output file.
- TEMPLATE: This is the project template used when generating the Makefile.app tells qmake to generate a Makefile targeted for a binary. If you are building a library, use the lib value.
In the ch02-sysinfo application, we started to use platform-specific compilation rules using the intuitive scope mechanism:
windows { SOURCES += SysInfoWindowsImpl.cpp HEADERS += SysInfoWindowsImpl.h }
If you had to do this with a Makefile, you would probably lose some hair before doing it right (being bald is not an excuse). This syntax is simple yet powerful, and is also used for conditional statements. Let's say you wanted to build some files on debug only. You would have written the following:
windows { SOURCES += SysInfoWindowsImpl.cpp HEADERS += SysInfoWindowsImpl.h debug { SOURCES += DebugClass.cpp HEADERS += DebugClass.h } }
Nesting the debug scope inside windows is the equivalent of if (windows && debug). The scoping mechanism is even more flexible; you can have the OR Boolean operator condition with this syntax:
windows|unix { SOURCES += SysInfoWindowsAndLinux.cpp }
You can even have else if/else statements:
windows|unix { SOURCES += SysInfoWindowsAndLinux.cpp } else:macx { SOURCES += SysInfoMacImpl.cpp } else { SOURCES += UltimateGenericSources.cpp }
In this code snippet, we also see the use of the += operator. The qmake tool provides a wide range of operators to modify the behavior of variables:
- =: This operator sets the variable to the value. The syntax SOURCES = SysInfoWindowsImpl.cpp would have assigned the singleSysInfoWindowsImpl.cpp value to the SOURCES variable.
- +=: This operator adds the value to a list of values. This is what we commonly use in HEADERS, SOURCES, CONFIG, and so on.
- -=: This operator removes the value from the list. You can, for example, add a DEFINE = DEBUG_FLAG syntax in the common section and in a platform-specific scope (say a Windows release) remove it with the DEFINE -= DEBUG_FLAG syntax.
- *=: This operator adds the value to the list only if it is not already present. The DEFINE *= DEBUG_FLAG syntax adds the DEBUG_FLAG value only once.
- ~=: This operator replaces any values that match a regular expression with the specified value, DEFINE ~= s/DEBUG_FLAG/debug.
You can also define variables in the .pro file and reuse them in different places. We can simplify this with the use of the qmake message() function:
COMPILE_MSG = "Compiling on" windows { SOURCES += SysInfoWindowsImpl.cpp HEADERS += SysInfoWindowsImpl.h message($$COMPILE_MSG windows) } linux { SOURCES += SysInfoLinuxImpl.cpp HEADERS += SysInfoLinuxImpl.h message($$COMPILE_MSG linux) } macx { SOURCES += SysInfoMacImpl.cpp HEADERS += SysInfoMacImpl.h message($$COMPILE_MSG mac) }
If you build the project, you will see your platform-specific message each time you build the project in the General Messages tab (you can access this tab from Window | Output Panes | General Messages). Here, we defined a COMPILE_MSG variable and referenced it when calling message($$COMPILE_MSG windows). This offers interesting possibilities when you need to compile external libraries from your .pro file. You can then aggregate all the sources in a variable, combine it with the call to a specific compiler, and so on.
windows:message($$COMPILE_MSG windows)
Besides message(), there are a few other helpful functions:
- error(string): This function displays the string and exits the compilation immediately.
- exists(filename): This function tests the existence of the filename. qmake also provides the ! operator, which means you can write !exist(myfile) { ... }.
- include(filename): This function includes the content of another .pro file. It gives you the ability to slice your .pro files into more modular components. This will prove very useful when you have multiple .pro files for a single big project.