Qt Compiles: Cute Shattering Sound When Compiler Upgrading
Posted On June 13, 2022
I have some Qt code that has been compiling for about 8 years now. On Windows. On Linux. On Macintosh.
The program is decently complex.
Once I upgraded my Macintosh to Monterey, everything started falling apart. Qt complained it has not been tested under the SDK, and I must upgrade. Sure enough, some things did not work. I brought out a VM and went backward, and Qt is compiling fine on older Mac XCodes. No problems whatsoever. It works on the newer OS just fine. Hmm. Apple is being Apple again.
So, I started pounding on it.
Qt has gone for hard core, extremely complex macros in their later code.
Obviously, as college students, if their macros broke previously compiling code in their Software Engineering classes, neither the TA’s nor the Professors cared. So, the current crop of Qt engineers are in the mindset that breaking things is fun and necessary. I believe they think it is acceptable to break currently compiling code. Who needs to compile old programs anyway?
To this end, please note that the current macros in Qt break the standard use of #include Fencing .
As you see above, once the preprocessor has parsed #define _fence_ it should skip the command to include this file the next time is is referenced by parsing #ifndef _fence_ and throwing away everything until it encounters the #endif.
In my experience, with Qt 6 and later, I found one situation where include file 1 included include file 2 which included include file 1. The error the compiler threw was a redefinition of a variable, which occurred in include file 1 because it was included from include file 2! Sorry, include file 1 should never have been parsed the second time around.
Fences Not Working
Obviously someone did something that undefined the fence, or uncovered a new compiler feature that maliciously breaks code.
Here is why the error occurred (at least for this problem):
define nullptr 0
By definition, 0 can stand in for any type of variable. Pointer. Float. Int. structure. anything.
But not in QT macros! And to show you how p*ssed they are that you did that, your fences are destroyed! If they only did that for a null pointer, fine. I almost understand. But they don’t!
So you either play by their rules, or go home. Simply don’t define nullptr. If you are smart enough, you can fork their codebase and fix their macros. I have had to fix several qt header files to get compiles, for stupid things like hash functions. Google is your friend in these cases.
Even More Interesting Issues
Don’t expect code that compiles under Qt 5.3 to compile under Qt 5.15, or 6.x. And that is ignoring the change from WebKit to WebEngine, and changes to deprecated functions which are no longer deprecated, but are removed. Deprecated used to mean not maintained. For qt, it means it will disappear on you on a minor dot revision.
That non-compiling code? Oh, yes, the Qt macros have gotten so complex that if you include other Qt headers in your MainWindow or your Dialog Class header files, you may not get a compile.
Also, depending upon the Qt version you are compiling with (especially on the Macintosh), you will either have to specify c++11, c++14, or c++17, or no version at all in order to escape errors in the Qt Header Files before you encounter 1 line of code in the .cpp file! The Qt header file macro that fails to compile under c++11 and c++17, will usually compile under c++14. If it compiles under c++17, it may fail to compile under any other version. (TLDR; You set a command line flag to tell the compiler to use that standard. Google it for your compiler version.)
Fixing Qt Headers Killing Your Compile
If you, like me, need to upgrade the Qt code base to move forward as OS and compilers get hideously more slow more complex, and more frustrating, then here are the steps to resolve the issues:
- Comment out all includes in your source file and your header file for the dialog/window you are trying to compile.
- Make sure all Qt classes in your object header file are pointers. If you must use Qt classes (such as QTimer, QElapsedTimer, etc.) in your header file, use pointers, don’t implicitly instantiate them. Add a forward reference; i.e. “class QTimer;” before your class definition to make the compiler happy. So, in your class, “Qtimer foo;” becomes “QTimer *foo;” Also don’t forget to do a “foo=new QTimer()” in your class constructor. If you kill that window, don’t forget to do a delete foo; in the destructor.
- Use an #if 0 / #endif fence to make your .cpp file mostly empty of compilable items.
- #include your class header, and class_ui header.
- On the Macintosh use “option-command-b” to compile just that file. (Saves time)
- If you need headers to get some definitions, put them in the .cpp file ahead of your included class header. Do not put them in the class header file unless you absolutely must. It very likely will kill your compile with weird errors as subsequent includes ignore fences as they circle back around.
- Once you get an empty code compile with no errors, move your #if 0 below the top function you blocked out (enabling it to be compiled, and the rest of the code still silent) and re-compile. Add headers at the end of your growing list in your .cpp file until you have no errors with this compile. Warnings are ok.
- Move the #if 0 down one more function, go back to step 7. Rinse and Repeat.
OR YOU CAN DO THE FOLLOWING ON A TEMPORARY BASIS:
Crank up a Virtual machine with a previous compiler and previous version of Qt. Compile and debug there. I found compiling on a OSx 10.8 or 10.10 allowed working on Monterey once you allowed for running unsigned applications. (Application signing is a money grab-bag for several companies.)
What you get by doing this is the ability to support your current OS, as well as previous OS’s. However, your pain in moving forward may be multiplied in the future. In my opinion, Cute may well be enjoying inflicting this pain.