So You Can’t Get Your Qt Models to Work With QML?

This particular rant was started by my looking to divert my mind by answering a question on StackOverflow. Never ever ever go to StackOverflow. Kids today just don’t know squat and there is nothing you can do to help them. Let’s start with my initial response an a slightly improved image.

The gist of the issue

QML is just a hand polished turd. There is no polite way to put it. Qt made a desperate ploy to attract script kiddies in the iDiot phone market with QML. The problem is, by and large, script kiddies don’t know anything. They tend to be “self-taught” with far more emphasis on “self” than “taught.” No grounding in the fundamentals of application design and software development. They just want to hack.

They like scripting languages because you can “just hack” without rules. If you want to get an idea for just how much disinformation exists in the script kiddie world, check out the claims about Python being type safe.

Hopefully you will all click the link and look at the original question and code. Please do so now!

My Initial Response

This image may not be 100% accurate but it is how one must visualize things in their mind. You have three distinct lanes an object lives in when you introduce QML: C++, QML Engine, and JavaScript engine. Each and every one of those lanes believes beyond a shadow of a doubt that they control the life and death of the object.

When you are just passing integers around that are passed by value, this is no issue. When you are passing QStrings around, because of the copy-on-write charter of Qt, this is only a minor issue. When you are passing real objects around, worse yet, complex containers of real objects, you have to understand this completely. The answer that “fixed” your problem really only masked it. You will find there is a lot of QML and JavaScript code out there that exists only to mask an application not respecting the lanes.

Had this application used an actual database there would be a fourth lane. Yes, SQLite provides an SQL interface and allows many things, but an actual database has an external engine providing shared access and controlling the life of cursors. SQLite files tend to be single user. Yes, multiple threads within your application can access it, but while your application is running you cannot open a terminal window and use command line tools to examine the database. Think of it more as a really nice indexed file system without sharing.

So, you create an object in C++ and then expose it to QML. The QML engine now believes beyond a doubt that it controls the life and death of that object despite not having its own copy.

QML is really feeble. It can’t actually do much, so it has to hand any significant object off to JavaScript. The JavaScript engine now believes beyond a shadow of a doubt that it now controls the life and death of that object.

You need to also envision these lanes as independent threads. There will most likely be many threads within each, but, in general, any signal or communication between these will go on the event loop of the target as a queued event. That means it will only be processed when it finally bubbles to the top of the queue for that event loop.

This, btw, is why you can never use Smart Pointers when also using QML/JavaScript. Especially the kind that do reference counting and delete the object when there are no more references. There is no way for the C++ lane to know that QML or JavaScript are still using the object.

The answer telling you to check for undefined property is masking the problem that your code is drunk driving across all lanes. Eventually, on a faster (or sometimes slower) processor garbage collection for one of the lanes will run at a most inopportune moment and you will be greeted with a stack dump. (The drunk driving code will hit a tree that does not yield.)

Correct Solution #1: Never use QML or JavaScript. Just use C++ and Widgets. Stay entirely within the C++ lane. If that is a route open to you it’s a good way to go. There is an awful lot of production code out there doing just that. You can obtain a copy of this book (or just download the source code from the page) and muddle through building it.

Correct Solution #2: Never actually do anything in QML or JavaScript. This is an all together different solution than #1. You can use QML for UI only, leaving all logic in C++. Your code is failing because you are trying to actually do something. I haven’t built or tried your code. I simply saw

function deleteRowFromDatabase(row)

which shouldn’t exist at all. C++ holds your model. You emit a signal from your QML/JavaScript lanes when user action requires deletion. This signal becomes a queued event in the C++ lane. When it is processed the row will be deleted and the model updated. If you have properly exposed your model to QML it will emit some form of “model changed” signal and the UI will update accordingly. One of the main points of MVC (Model-View-Controler) is communication to/from the model. When the data changes it notifies the view(s).

Correct Solution #3: Never use C++. Have your C++ be a “Hello World!” shell that just launches your QML. Never create and share an object between C++ and the other two lanes. Do everything inside of JavaScript and QML.

Binding loop errors, in general, happen when code drunk drives across all three lanes. They also happen when code doesn’t view each lane as at least one distinct thread with its own event loop. The C++ lane hasn’t finished (perhaps not even started) the delete operation but your code is already trying to use the result.

The Final Solution

Lots of back and forth happened. When someone has locked themselves into a failed architecture they are generally the last ones to see the failure of their plan. I know. I’ve been on the other side of that. You have to be on the other side of that a few times to learn how to avoid it in the future.

First problem is they chose to use QML. That locked them into using a model. You will find out why that was bad in a bit.

Second problem was drunk driving across all of the lanes. When you stay in the green lane, life is good. Theoretically, when you stay in the red lane life is just as good, you simply can’t do anything real. If you choose to exist only in the yellow lane, then you don’t need Qt at all.

Third problem was the fact there was absolutely no reason for a proxy. You are supposed to use a proxy when you need to transform data for display, like formatting a date or populating a combo box of reference table values. Some also use them to ensure a read-only data source cannot accidentally receive a write request.

Fourth problem was not really knowing anything about a relational database. I see this a lot in the self-taught universe. I even see it with recent college graduates because they can pursue some kind of Web/Game development degree and nobody thinks to include a course on how to care for and feed a relational database.

The Models

QML Turd

There is just no polite way to describe QML. This forcing of models upon the world would be fine if it was properly architected. Hell, suitable debugging and error information would go a long way.

QVariant data(const QModelIndex &index, int role) const;

That one line causes more problems than anything else. If it doesn’t exactly match that in your concrete implementation, you will get no data. The documentation and the IDE will lead many to believe it should have a different signature.

“Oh, but if it’s not right it will just give me an error, right?”

Nope!

If you are using this model in a table the model will return the row count and your table will be all NULL data. If QML can’t find this, it won’t tell you. Adding insult to injury there is not a flag or debugging option you can set to force QML to identify which data() method it used.

So, if you have 60 rows in your table the model will tell the UI there are 60 rows and you will have 60 NULL rows and, most likely a scrollbar.

Broken

This is where OOP jumped the shark. You can get my latest book and read about MVVM. I won’t repeat that essay. What I will tell you is that MVVM was created by people who don’t know how to use a relational database and are physically incapable of understanding data, without a relation, is useless.

If someone walks into a room and says “25” then leaves you have no idea WTF they are talking about. If someone walks into a room and says “John Smith is 25” then leaves, you may or may not know who John Smith is, but you now know he is 25. The 25 now has meaning because it has a relation. With the possible exception of NULL, there is no meaning without relation.

You need to know the previous information because it provides a frame of reference. People who don’t know how to properly use relational databases create the models. When they create automated tests for them these tests will be worse than non-existent.

void SQLiteModel::deleteRow(int rowNo)
 {
     qDebug() << "removeRow called for " << rowNo << "\n";
     bool rslt = removeRow(rowNo);
     qDebug() << "result of removeRow() " << rslt << "\n";
     qDebug() << "Error: " << lastError().text() << "\n";
     qDebug() << "query: " << query().lastQuery() << "\n";
     submitAll();
 }

When the above executes you see the following.

removeRow called for  3
 result of removeRow()  false
 Error:  "near \"WHERE\": syntax error Unable to execute statement"
 query:  "select * from questions;" 

It didn’t matter if the table was created with this

query.exec("CREATE TABLE IF NOT EXISTS questions (Q_NO INTEGER PRIMARY KEY, Q_TEXT TEXT);"); 

or with this

query.exec("CREATE TABLE IF NOT EXISTS questions (Q_NO INTEGER PRIMARY KEY AUTOINCREMENT, Q_TEXT TEXT NOT NULL);" 

It appears this has been broken for over a year, if it ever worked at all.

Lack of Formal Training

Formal training. Actually attending a good school for computer science cannot be overvalued. Sadly there are a lot of run-for-profit shit schools along with just plain shit schools.

Formal training. That’s what teaches you to generate large test data sets. Testing with only ten or fewer records won’t help you find anything. Even for some hokey little program like this you need to initially test with at least enough records to force a scrollbar onto the UI. At least 60 would be good. 500 would be better. There is an ipsum generator if you really hate typing that much.

Formal training. That’s what teaches you the first step to solving a database table issue of any kind is verifying the creation and population of the table from the command line. If you read through the original code you will see it was just stuffing records into the proxy.

Formal training. That’s what teaches you the “default debugger” and moving onto other I/O operations like deleting rows. Other things that can be verified from the command line.

Formal training. That is what teaches you proper software development technique. The language and tools you use don’t matter. Proper technique is what matters.

You can solve any programming problem with the proper technique formal training provides. That and a formal logic class that teaches no programming language is what one needs to succeed in IT. Tools come and go. Those two things are constants.

The Code

QT += quick sql
 CONFIG += c++11
 #
 This is important
 #
 CONFIG += qmltypes
 QML_IMPORT_NAME = Fred
 QML_IMPORT_MAJOR_VERSION = 1
 You can make your code fail to compile if it uses deprecated APIs.
 In order to do so, uncomment the following line.
 DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 SOURCES += \
         main.cpp \
         sqlitemodel.cpp
 RESOURCES += qml.qrc
 Additional import path used to resolve QML modules in Qt Creator's code model
 QML_IMPORT_PATH =
 Additional import path used to resolve QML modules just for Qt Quick Designer
 QML_DESIGNER_IMPORT_PATH =
 Default rules for deployment.
 qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
 HEADERS += \
     sqlitemodel.h
ifndef SQLITEMODEL_H
 define SQLITEMODEL_H
 include 
 class SQLiteModel : public QSqlTableModel
 {
 Q_OBJECT
 public:
     explicit SQLiteModel(QObject *parent = NULL);
     ~SQLiteModel();
 bool setQuery(const QString &query); void defaultQuery(); virtual QHash<int, QByteArray> roleNames() const; QVariant data(const QModelIndex &index, int role) const; void declareQML(); Q_INVOKABLE QString getQuestion( int rowNo);
 public slots:
     void deleteRow( int rowNo);
     void copyRow( int rowNo);
     void updateRow( int rowNo, QString txt);
 private:
     bool openDb();
     void addStarterData();
 QSqlDatabase db; int m_recordCount;
 };
 endif // SQLITEMODEL_H
include "sqlitemodel.h"
 include 
 include 
 include 
 include 
 include 
 include 
 include 
 include 
 include 
 include 
 void SQLiteModel::declareQML()
 {
     qmlRegisterSingletonInstance("Fred", 1, 0, "MyModel", this);
 }
 SQLiteModel::SQLiteModel(QObject *parent)
     :QSqlTableModel(parent)
 {
     setEditStrategy(QSqlTableModel::OnFieldChange);
     openDb();
 }
 SQLiteModel::~SQLiteModel()
 {
 }
 bool SQLiteModel::openDb()
 {
     const QString DB_NAME = "test.db";
     QDir hDir = QDir::home();
     bool retVal = false;
 db = QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(hDir.absoluteFilePath(DB_NAME)); retVal = db.open(); if (retVal) {     QSqlQuery query(db);     query.exec("CREATE TABLE IF NOT EXISTS questions (Q_NO INTEGER PRIMARY KEY AUTOINCREMENT, Q_TEXT TEXT NOT NULL);");     query.exec("SELECT COUNT(*) FROM questions;");     while (query.next())     {         QSqlRecord rec = query.record();         int recordCount = rec.field(0).value().toInt();         if (recordCount < 10)         {             addStarterData();         }     } } return retVal;
 }
 bool SQLiteModel::setQuery(const QString &query)
 {
     QSqlQueryModel::setQuery(query);
 if (this->query().record().isEmpty()) {     qWarning() << "SQLiteModel::setQuery() -" << this->query().lastError();     return false; } m_recordCount = record().count(); return true;
 }
 void SQLiteModel::defaultQuery()
 {
     setQuery("select * from questions;");
 }
 QHash SQLiteModel::roleNames() const
 {
     QHash roles;
 for( int i = 0; i < record().count(); i++) {     roles[Qt::UserRole + i + 1] = record().fieldName(i).toLatin1(); } qDebug() << "Roles: " << roles << "\n"; return roles;
 }
 QVariant SQLiteModel::data(const QModelIndex &index, int role) const
 {
     QVariant value = QSqlQueryModel::data(index, role);
     if(role < Qt::UserRole)     {         value = QSqlQueryModel::data(index, role);     }     else     {         int columnIdx = role - Qt::UserRole - 1;         QModelIndex modelIndex = this->index(index.row(), columnIdx);
         value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
     }
 return value;
 }
 void SQLiteModel::addStarterData()
 {
     QSqlQuery preparedQuery(db);
     QStringList lst;
 lst << "Why is the sky blue"     << "What color is the sky on your planet"     << "What makes water wet"     << "Do you drink tea"     << "Is left the opposite of right"     << "If Fred is a boy, what is the name of his dog"     << "Who are you"     << "What do you want"     << "Which way is up"     << "Who knows what evil lurks in the hearts of men"     << "Two wrongs don't make a right, but do three lefts"     << "If boiling has a point, what is it"; preparedQuery.prepare("INSERT INTO questions (Q_TEXT) VALUES(:txt);"); for (QString txt : lst) {     preparedQuery.bindValue(":txt", txt);     preparedQuery.exec(); }
 }
 QString SQLiteModel::getQuestion(int rowNo)
 {
     QSqlRecord rec = record(rowNo);
     return rec.field("Q_TEXT").value().toString();
 }
 /* Qt's view of the world really kind of sucks when it comes to
 relational databases. They want you to do everything within the model.
 A real programmer wants to do everything directly at the database
 and let some watchdog re-run selects as necessary. Admittedly that
 approach is really sucky for the user when a table has a billion rows
 and the query is SELECT * FROM. That approach, however, understands
 that it is not a single user world. In the real world hundreds of
 users will be adding/deleting/modifying records. Each situation has to
 decide how they want to monitor and update the model.
 *
 There are various rules in the documentation as to when Qt will
 update the database. Call submitAll() for each change and remove
 all doubt.
 *
 I put a huge block of comments here because Qt models, at least from
 what I've seen well and truly suck when the primary key is an
 auto-increment integer. You physically have to write to the database
 to get the next key value.
 *
 The ultimate insult here is that
 *
 ONE SHOULD NEVER DO DATABASE I/O IN THE MAIN EVENT LOOP
 *
 This is a constant problem with Qt's design and its developers. When you
 work on real computers with massive distributed databases, you understand
 one query could take as long as half an hour, especially with a "BigData"
 database where many spindles are spun down. This is why banks and other
 institutions make you submit a query when you want something from outside
 the tiny amount they keep online. Later in the day you get a message
 saying your report is ready.
 *
 Adding further insult to injury, the models are busted.
 *
 removeRow called for  3
 *
 result of removeRow()  false
 *
 Error:  "near \"WHERE\": syntax error Unable to execute statement"
 *
 query:  "select * from questions;"
 *
 Every table modification fails with that exact error and you can't dump the
 query to see just where the problem really is.
 *
 */
 void SQLiteModel::deleteRow(int rowNo)
 { 
 if 0
 qDebug() << "removeRow called for " << rowNo << "\n"; bool rslt = removeRow(rowNo); qDebug() << "result of removeRow() " << rslt << "\n"; qDebug() << "Error: " << lastError().text() << "\n"; qDebug() << "query: " << query().lastQuery() << "\n"; submitAll();
 else
 QSqlRecord rec = record(rowNo); QSqlQuery query(db); query.prepare("DELETE FROM questions WHERE Q_NO = :rowNo ;"); query.bindValue(":rowNo", rec.field("Q_NO").value().toInt()); bool rslt = query.exec(); qDebug() << "Result of removing row: " << rslt << "\n"; defaultQuery();
 endif
 }
 void SQLiteModel::copyRow(int rowNo)
 {
 if 0
 QSqlRecord rec = record(rowNo); QSqlRecord rec2 = record(); rec2.setValue("Q_TEXT", rec.field("Q_TEXT").value().toString()); bool rslt = insertRecord(rowCount(), rec2);  // negative appends to end qDebug() << "result of inserting row: " << rslt << "\n"; qDebug() << "Error: " << lastError().text() << "\n"; qDebug() << "query: " << query().lastQuery() << "\n"; if (rslt) {     qDebug() << "submitting changes\n";     submitAll(); }
 else
 qDebug() << "copyRow called for " << rowNo << "\n"; QSqlRecord rec = record(rowNo); QSqlQuery query(db); query.prepare("INSERT INTO questions (Q_TEXT) VALUES(:txt);"); query.bindValue(":txt", rec.field("Q_TEXT").value().toString()); bool rslt = query.exec(); qDebug() << "Result of copying row: " << rslt << "\n"; defaultQuery();
 endif
 }
 void SQLiteModel::updateRow(int rowNo, QString txt)
 {
     qDebug() << "updateRow called for " << rowNo << "  with: " << txt << "\n";
 if 0
 QSqlRecord rec = record(rowNo); rec.field("Q_TEXT").setValue(txt); bool rslt = setRecord(rowNo, rec); qDebug() << "result of update: " << rslt << "\n"; qDebug() << "Error: " << lastError().text() << "\n"; qDebug() << "query: " << query().lastQuery() << "\n"; if (rslt) {     submitAll(); }
 else
 QSqlRecord rec = record(rowNo); QSqlQuery query(db); query.prepare("UPDATE questions SET Q_TEXT = :txt WHERE Q_NO = :rowNo ;"); query.bindValue(":txt", txt); query.bindValue(":rowNo", rec.field("Q_NO").value().toInt()); bool rslt = query.exec(); qDebug() << "Result of removing row: " << rslt << "\n"; defaultQuery();
 endif
 }
 p, li { white-space: pre-wrap; } 
 #include <QGuiApplication>
 #include <QQmlApplicationEngine>
 #include <QQmlContext>
 

 

 #include "sqlitemodel.h"
 

 int main(int argc, char *argv[])
 {
     QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
 

     QGuiApplication app(argc, argv);
 

     QScopedPointer<SQLiteModel> sqmodel( new SQLiteModel(nullptr));
     sqmodel->defaultQuery();
     sqmodel->declareQML();
 

     QQmlApplicationEngine engine;
     const QUrl url(QStringLiteral("qrc:/main.qml"));
     QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                      &app, [url](QObject *obj, const QUrl &objUrl) {
         if (!obj && url == objUrl)
         {
             QCoreApplication::exit(-1);
         }
     }, Qt::QueuedConnection);
 

 

     engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
 

     QList<QObject *> objs = engine.rootObjects();
 

     if (!objs.empty())
     {
         QObject *root = objs[0];
         QObject::connect( root, SIGNAL(deleteRow(int)), sqmodel.get(), SLOT(deleteRow(int)));
         QObject::connect( root, SIGNAL(copyRow(int)), sqmodel.get(), SLOT(copyRow(int)));
         QObject::connect( root, SIGNAL(updateRow(int, QString)), sqmodel.get(), SLOT(updateRow(int, QString)));
     }
 

     return app.exec();
 }
 
import QtQuick 2.15
 import QtQuick.Window 2.15
 import QtQuick.Controls 1.4
 import QtQuick.Controls 2.15
 import QtQuick.Controls.Material 2.15
 import Qt.labs.qmlmodels 1.0
 import Fred 1.0
 ApplicationWindow
 {
     id: root
     title: qsTr("SQL Demo")
     width: 640
     height: 480
     visible: true
 minimumWidth: 640 minimumHeight: 480 signal deleteRow(int rowNo) signal copyRow(int rowNo) signal updateRow(int rowNo, string txt) TableView {     id: myView     anchors.fill: parent     TableViewColumn{ role: "Q_NO"  ; title: "Primary Key" ; width: 80 }     TableViewColumn{ role: "Q_TEXT"  ; title: "Question" ; width: 400 }     model: MyModel     Menu     {         id: contextMenu         property int rowNo: 0         property string rowTxt         MenuItem         {             text: qsTr('Delete')             onClicked:             {                 root.deleteRow(contextMenu.rowNo)             }         }         MenuItem         {             text: qsTr('Edit')             onClicked:             {                 console.log("Edit clicked for row " + contextMenu.rowNo)                 promptQuestion.rowNo = contextMenu.rowNo                 promptQuestion.rowTxt = contextMenu.rowTxt                 promptQuestion.visible = true             }         }         MenuItem         {             text: qsTr('Copy')             onClicked:             {                 console.log("Copy clicked for row " + contextMenu.rowNo)                 root.copyRow(contextMenu.rowNo)             }         }         MenuItem         {             text: qsTr('Cancel')             onClicked:             {                 console.log("Cancel clicked for row " + contextMenu.rowNo)             }         }     }     rowDelegate: Item     {         Rectangle         {             anchors             {                 left: parent.left                 right: parent.right                 verticalCenter: parent.verticalCenter             }             height: parent.height             color: styleData.selected ? 'gray' : styleData.row % 2 ? 'white' : 'cornsilk'             MouseArea             {                 anchors.fill: parent                 acceptedButtons: Qt.RightButton                 onClicked:                 {                     mouse.accepted = false                     if (mouse.button == Qt.RightButton)                     {                         console.log("caught right button")                         mouse.accepted = true                         contextMenu.rowNo = model.row                         contextMenu.rowTxt = MyModel.getQuestion(model.row )                         contextMenu.popup()                     }                 }             }         }     } } Dialog {   id: promptQuestion   title: "Edit Question"   standardButtons: Dialog.Ok | Dialog.Cancel   modal: true   property int rowNo: 0   property string rowTxt   TextField   {       id: userTxt       text: promptQuestion.rowTxt   }   onAccepted:   {       console.log("Accepted")       root.updateRow(rowNo, userTxt.text )       promptQuestion.close()   }   onRejected:   {       promptQuestion.close()   } }
 }

Commentary

Too many of you will take this code and paste it into your own projects without even looking at it. You really need to read the comments found in sqlitemodel.cpp at lines 150 through 194.

I didn’t know the models were this busted because I never use them. I only use QML at gunpoint and even then I have to think about it.

The models are a failed architecture because they must run in the main event loop. Never do significant I/O in the main event loop. Yeah, this works nice with a few dozen records. Probably work nice with a few thousand on a fast machine that has plenty of RAM for disk caching and no other users needing to do I/O.

If your database is accross the Internet where you could have upwards of 20 second network timeouts your UI is locked up for that entire time. If your source data is stored on something like an IBM Data Cell;

Or its modern day replacement, the tape library

Tape library robot in action

your UI could be locked up for over half an hour waiting its turn in the I/O request queue. You don’t know the definition of “Big Data” until you are writing applications designed to use that amount of data. Despite what you may think, there are a lot of these still around. Chapel Hill didn’t retire Roberta until 2019.

Roberta at Chapel Hill

The “Data Appliance” which replaced Roberta isn’t going to be oceans faster. Faster, yes. Oceans faster, not really. Has to do with the compressing and de-duplicating.

Here is a tape robot library that just got installed in 2019.

The extra capacity needed to be at least 60 petabytes (PB) – the equivalent of storing more than 10 million DVDs – with the potential to increase again to meet future data demands.

From the above link
The Minimum You Need to Know About Qt and Databases

The Bottom for Technical Recruiting Just Keeps Getting Lower

Ordinarily I wouldn’t drag something this specific onto my geek blog, but every time I try to reply to the pimps email it bounces.

gandi.net doc

I had never heard of gandi.net. Given how well their email works I suspect they won’t be around for very long. Why do I need to have an unsubscribe link when I reply to email that came through their system?

How does an automatic filter identify “poorly organized content” anyway?

At any rate, I doubt this pimp will be around that long. Here’s a few rather obvious reasons.

Nice tag line, but you have to actually earn such a tag line.

no https

I tried, they don’t have an https:// address. Even I bothered to do that and I hate having to mess with my Web sites. Despite what people tell you, it’s a lot of work when you have Web pages that specify the full path to other Web pages on your site. Relative pathing is insecure but it is a cheat I see in many Web programming examples.

So, basically this pimp called because one of my alerts popped up they were now shopping around a gig that I believe has been open for two years. It looked like the client dropped some of the more worthless requirements so I responded. Really bad clients do this. They bounce an opening from pimp to pimp to pimp unwilling to believe they are offering too little money for too many skills. Another massive mistake bad clients make is wanting senior developers to use AGILE. Professionals won’t touch AGILE with a hundred mile pole. No, getting paid doesn’t make you a professional.

This person at least spoke native English which was refreshing. They left a message. When I called back I didn’t hear “The Magic Jack Customer you have called…” so I had a bit more hope than I do with the illegal alien firms. Hope that was quickly dashed. It was a short phone call. Recruiter tried to play the “professionalism” card. You have to be an actual professional to play that card.

Just how up on technology is a firm that hasn’t bothered to get an https:// address? Admittedly it is not that secure, but it is a touch better than nothing.

I tried to respond to the email they sent me, twice, but that gandi mail service (which won’t be around for long) wouldn’t let either email through.

What really sent the gopher down the mountain was someone thinking all they needed to be a technical recruiter was a telephone. They believed that they could read verbatim from the script of questions and that the questions would be good and just. Said questions generally are provided by account managers that know even less about technology than the recruiters. Those questions are never right.

Senior Developers

Senior developers, those with 30+ years in the field who have other interests in life take a lot of time off. Some of us write an award winning technical book series during the off time. Others work on OpenSource projects like that CopperSpice and Diamond thing you’ve been reading about on here.

At this stage of a senior developer’s career you only take projects that are interesting. You work really hard at a high billing rate for 6-14 months then you take 8-24 months off doing other things. It’s called semi-retired in some circles. I call it a stress free life. Coworkers can’t understand how I can join a project putting in 70-90 hours per week for almost the entire length of the project. Well, it is because I do these other things to decompress between projects.

Being able to come home to the family farm really helps as well. Yeah, I get drafted into an awful lot of grunt labor while I’m here, but it is different. (We just finished harvest btw, and I just got my new title pushed out the door so it is time to get back under contract.)

Senior developers have more past than future in their careers. We’ve done lots. Things that interest us we continue to do. We aren’t built in such a way we could fully retire even if we have the $8+million a person needs to retire in America.

Respect and Professionalism

It is the height of disrespect to not bother learning anything about the industry for which you recruit.

It is the definition of unprofessional to assume you can work from a short list of questions provided by someone else.

Only the lowest of the low try to chisel a remote rate down to illegal alien wages then expect the consultant to travel on-site out of state for the same shitty rate.

Seriously, if you are going to be recruiting C++ developers then you need to read an overview of what C++ really is. “Just another programming language” ain’t gonna win you qualified candidates. You need to be able to vet the questions you have been given to ask. Some of the really bad ones you can vet with a quick Web search.

C++ Does not have versions, compilers do

Here’s a little snippet from wikipedia

Zortech C++ compiler

That is the first C++ compiler I worked with. I upgraded from their DOS C compiler. You will notice the compilers have versions.

gnu c++ compiler version

It is far too long to include in a single email, but Gnu and most other compilers have command line switches controlling which STANDARD is used.

When working with CopperSpice one must use c++17 and that is what I’ve been using lately. When working at/for/with medical device clients I tend to use c++11. The medical device world tends to not skim the bleeding edge; favoring security and stability over new and cool.

Traveling Consultant

When one is a traveling consultant they take corporate housing. This means they have to work 1099 instead of W-2 because under W-2 travel expenses are not tax deductible as a business expense. (If you have been telling people to do that or have been doing it yourself expect a rather brutal conversation with the IRS. Bring a lawyer because you might be going to jail.)

Corporate housing is very expensive.

One must also pay income tax in multiple states. You really need a tax professional to handle all of that for you. Also leave any over payment in the hands of the state and federal revenue services. Nope, nope, nope, that isn’t your savings for a big end of year trip or splurge. That’s your insurance policy. If there turns out to be some horrific mistake on your taxes you can pass the red face test.

Truth be told, unless it is a catastrophic mistake, you aren’t even asked to take the red face test. You just get a letter in the mail from the IRS (or your state version) saying “according to our records this isn’t allowed so your carryover is now this amount.” Hang onto that letter and hand it to your tax person next year. Know that this will happen periodically. Sometimes it will be your fault and other times the agency’s mistake. Consider it a cost of doing business and move on.

When I’m going to a place I’ve never been before, after staying in a hotel for the first couple of weeks so I have an escape hatch if we don’t like each other, I book corporate housing. National isn’t the highest of the high end nor are they low. They are really more upper middle.

National rates for boulder

It has been my experience that those “starting from” prices are for the studio apartments even when you tell the search you want at least a one bedroom. Please re-visit my three part series “Calculating Your Minimum Billing Rate.”

Part 1

Part 2

Part 3

Know that I wrote than in 2011 after a similar dispute with a pimp. Your first week must pay for a full month of corporate housing.

31 days * $130-per-day = $4030 / 40 hours = $100.75

No, you can’t spread it across the entire month because the other weeks have to cover your other expenses.

No, you can’t make Extended Stay America work for six-plus months. I use them for short contracts of three months or less. You usually get a lot of construction crews in them though. At 4am on Monday morning the fire alarm goes off because some fool didn’t read the sign about turning the fan on high before starting to cook bacon. Plan on brutal Monday mornings when you stay there.

One can make Residence Inn or Candlewood Suites work for six plus months if you don’t mind communal laundry. Just know that you will have a studio without an oven. You can’t bake Cod fish for a healthy supper nor can you heat up a frozen pizza. Electric stove top or a microwave are your cooking options. I point this out so you don’t fall into the price trap.

Candlewood Suites option
31 days * $75-per-day = $2325 / 40 = $58.125

The hidden cost here is the roughly dollar per load to wash clothes and another dollar or so to dry each load. More importantly you will be eating most of your meals out unless you want to subsist on salads and microwave dinners.

Rolling Over

Don’t get me wrong, I like Candlewood Suites. I’ve used them quite a bit over my career. I go in with my eyes wide open though. I know I will be eating out almost every meal. (I’ve never tried to cook bacon in one either.) When I know a gig will not run longer than four months and it is in an area where none of the corporate housing companies I talk with has a unit rolling over, I first look for Candlewood. I have consulting friends who love Residence Inn; I just can’t make the numbers work for them. I guess I love money more than I love the place?

Please don’t talk to me about AirBnB or any of these other room rental places. That’s always a tragedy. They definitely aren’t long term options. As a general rule the only place within a reasonable commute of your client site will fall into one of these categories.

  • Someone with a zillion kids looking to make a buck on the side by having you take the “spare” room the kids keep scattering toys in.
  • The elderly couple who want you to spend each evening meal with them telling them stories of all the places you’ve been and things you’ve seen . . . until you run out of stories.
  • That 400+ pound dude with enough back hair to be mistaken for an ape and you know this because they have a penchant for walking around in red bikini underwear that come out of a plastic tube.
  • A “basement unit.”
  • An in-laws apartment.

During my college days I rented an in-laws apartment. They are typically created by people who hate their in-laws. You typically find them in bungalow type houses. Someone adds a few dormers to the attic. A bathroom and a tiny kitchen area along with a sleeping area and a “living” area. No air conditioning. One tiny little stick out the wall heater but you are supposed to just let the heat drift up from the first floor through a tiny opening.

A college kid can do something like this for a while. A senior developer cannot. After a week you won’t be working at the level the client needs you to work and you will be canned.

The only good way to save money on corporate housing is to have a long term relationship with a housing company.

Most people have a complete misconception about corporate housing. While there are some companies that have individual units with furniture and things previous owners left there; renting it out time and time again; that is not most. The previous type companies deal only in wire transfers or company checks via ACH. You can get a slight deal if you have a big checking account, but the quality of stay will vary rather dramatically. Most will be condos in the “cheaper areas” of a location. I have had pleasant stays in some, but . . . you have to know what you are getting into.

Your primary type of corporate housing company has leasing arrangements with newer apartment complexes. They also have leasing arrangements with furniture leasing companies. You have to sign for a minimum three months or pay a real premium. They need anywhere from 48-hours to two weeks to set up a unit for you. They take credit cards. After the initial three months they will let you do month by month.

All of the furniture and dishes are new or very very close to it. One corporate housing company told me I could take any of the dishes I wanted when I moved out. They said I had paid for them moving in and they were just going to take them to a second hand store. I didn’t take anything because, just how much of that stuff do you need?

These companies have standards. When you are a new customer they set up a new unit, period. You will be quoted list price. When you are an old customer who has rented many times, you can call a rep and ask for a roll over unit. If you have a fantastic relationship with the company you can tell them “just clean it.” Your move-in fees will be really low. You will get a slightly better deal on the rent. If your relationship is only good or the rep is new, they will inside on putting new bedding and towels in the unit. Fees will still be lower than new and you may get a break on the rent. You definitely won’t pay the advertised price from the Web site.

You are still going to pay a lot of money.

Corporate housing is not a $50/night low budget hotel. It is also not “a furnished apartment” like many apartment complexes try to pass off. You give them your credit card, fill out a little form for the background check, wait a few days. Then you show up with your clothes and computers. Everything else is there and ready to go. Find a grocery store and go shopping. You have a washer and dryer in your unit. You have parking. You have television (usually in both living room and bedroom).

More and more corporate housing companies have come to the realization they actually have to cater to business travelers and that business travelers need a desk with an actual office chair.

Desk and chair national set me up with

After years of begging various corporate housing companies and even sending some links to used office furniture shops near the housing unit telling them they could bill me $70 for the desk and $100 for the chair, National got me a brand new desk with a second hand chair. That’s why I’m talking National up so much here and why I look to them first for pricing. I know I can get an actual desk.

I got really lucky with Apex Corporate Housing on one gig. They are a cash and carry type corporate housing company, no credit cards. The unit they had for me was a two bedroom and it had two desks.

Primary desk

Keep in mind that a traveling consultant goes on-site to work like a dog and make a lot of money. You aren’t a tourist. You aren’t there to try out all of the restaurants and see all of the attractions. You went there for billable hours. Your cost of living is more than twice that of the locals. If you are like me with a book series and OpenSource projects, your “off time” you spend working on new books, writing blog posts, and doing a bit of OpenSource coding.

I love going on-site in the winter! Especially when I can get in-building parking!

CopperSpice Experiments – Pt. 16

Now we must create a minimal GUI “Hello World” to use testing out the theory. It will also be needed as a bug report test case if this turns out to be a bug rather than “should have had this switch on when building CopperSpice.”

In a terminal I did the following:

mkdir cs_gui_hello
mkdir cs_gui_hello/src
mkdir cs_gui_hello/resources
mkdir cs_gui_hello_debug
mkdir cs_gui_hello_build
cp diamond/resources/*.gif cs_gui_hello/resources

cd cs_gui_hello

roland@roland-amd-desktop:~/Projects/cs_gui_hello$ cp ../cs_hello/*.txt .
roland@roland-amd-desktop:~/Projects/cs_gui_hello$ cp ../cs_hello/*.md .
roland@roland-amd-desktop:~/Projects/cs_gui_hello$ 

In both CMakeLists.txt files I changed all cs_hello to cs_gui_hello and changed all Cs_Hello to Cs_GUI_Hello. I also updated the README.md. Then I renamed the build_info input file to: cs_hello_build_info.h.in and changed the important part of it to read:

#ifndef CS_GUI_HELLO_BUILD_INFO_H_IN
#define CS_GUI_HELLO_BUILD_INFO_H_IN

// Cs_GUI_Hello Version  "x.y.z"
constexpr const char *versionString = "@PACKAGE_VERSION@";

// Cs_GUI_Hello Build Date "08/17/2017"
constexpr const char *buildDate = "@BUILD_DATE@";

#endif

The copy of the *.gif earlier was just to get the animated gif files to test with. Since I really needed only one file I manually created cs_gui_hello.qrc in a text editor.

<RCC>
    <qresource prefix="/animations">
        <file>resources/spinning-red-diamond-4.gif</file>
    </qresource>
</RCC>

That means there was one other change in the CMakeLists.txt file in the src directory. The following lines had to be uncommented and a new source file has to be added.

Have to compile qrc to source and add it to source list

Now we open a terminal and test the assumption.

cd ~/Projects/cs_gui_hello_build

cmake -G "Ninja" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=../cs_gui_hello_debug -DCMAKE_PREFIX_PATH=/usr/lib/cs_lib/lib/cmake/CopperSpice ../cs_gui_hello

ninja
ninja install

valgrind --tool=memcheck --trace-children=yes --leak-check=full --show-leak-kinds=all --log-file=/home/roland/hello.log ./../cs_gui_hello_debug/cs_gui_hello
What I see in CopperSpice

No, I didn’t run it in valgrind the first few times. I did towards the end because I wanted to point out a bad thing in the code. It is code like I see in many many places on the Internet. You are in a hurry and bang something out fast then create this kind of self replicating screw-up as many many people find and re-use your stuff because it “seems to work.”

The code

Yes, I deliberately pasted an image so you couldn’t just scrape the code and go! I need to paste a few more images so you have a frame of reference. I went to the bottom of the log file and searched backwards for “lost.”

memory leak one
memory leak two
memory leak 3
Repeating memory leak

Hopefully you can zoom in on the images to see the 10+K leak in the first image and the 700+ byte leak in the second followed by a pair of 500+ byte leaks. What is interesting about the last 512 byte leak image is that it repeats for every disk block read for the gif.

All of this happened because of line 19 (or so I thought). QMovie doesn’t provide a constructor with a non-optional parent pointer as the first parameter. If you want to create in one line much like I did at line 19 you have to provide an empty QByteArray then the parent.

Now we get to why I pointed out the qDebug() statement in the last post.

no supported formats

There are no supported formats. I am hoping there is just a switch needed for compilation of CopperSpice that was missing when I built. It would be odd for the documentation to be way ahead of the implementation.

I changed the code to see if parentage would fix the leak because you can’t really do much cleanup when the source ends with:

return a.exec();

I have had to do cleanup over the years. You end up coding something like:

int retVal = a.exec();
/*
 *  Do all of of your cleanup paying special attention
 * to the fact you no longer have an event loop so much
 * of the Qt cleanup won't work because a surprising amount
 * queues an event.
 */
return retVal;

I changed the code to read as follows:

#include <QApplication>
#include <QMainWindow>
#include <QLabel>
#include <QVBoxLayout>
#include <QMovie>
#include <QByteArray>


int main( int argc, char *argv[] )
{
    QApplication a( argc, argv );

    QMainWindow mw;

    QLabel *hello = new QLabel( "Hello World!" );

    qDebug() << "supported formats: " << QMovie::supportedFormats();

    QLabel *movieLabel = new QLabel();
    QByteArray emptyFormat;
    movieLabel->setMovie( new QMovie( "://animations/spinning-red-diamond-4.gif", empty_format, movieLabel ) );

    QVBoxLayout *layout = new QVBoxLayout();

    layout->addWidget( hello );
    layout->addWidget( movieLabel );

    QWidget *w = new QWidget();

    w->setLayout( layout );
    mw.setCentralWidget( w );

    mw.show();

    return a.exec();
}

Not only did I have all of the same leakage, there was new leakage for the QByteArray. This is the problem with these quick little test programs. Just a few lines of code gives you a whole lot of leakage.

Few developers take the time to actually run valgrind or any other memory checking tool. Using tools like this in an AGILE world is something that is “never in the sprint.”

To be fair, I went over to the other machine and ran valgrind on that little demo. There were lots of little leaks that seemed to all be in some Qt internal thing. Nothing I could really “fix.”

valgrind --tool=memcheck --trace-children=yes --leak-check=full --show-leak-kinds=all --log-file=/home/roland/hello.log ./gui-hello

The ideal situation is for your application, no matter how small, to never have the word “lost” on any of the blocks in your valgrind report. You won’t be able to get rid of “still reachable” without severely impacting performance. This generally happens because the compiler tool chain has its own memory management library helping new/malloc out. Instead of allocating RAM from the system it goes and gets a big chunk then doles it out per each request your application makes.

Sometimes there is even a switch you can add to the build command line to set the size. If you’ve done some stats or simply wrote a hokey little “free memory” slot hooked to a timer so you “know” about how much free memory their is during your test runs, you can set the initial allocation to be

(TOTAL_MEMORY - Smallest_Free_Memory - Estimated_Binary_Load_Size) 

This will gain you a bit of performance. On a desktop running on grid power with high quality RAM chips, you won’t gain a lot. On an embedded system using the lowest power RAM one could find to conserve battery life, you can breath new life into the application by finding and using such a setting. Dynamic allocation on battery powered embedded targets typically sucks. This is especially true when the system has no GPU for some reason.

mainwindow.h
mainwindow.cpp

So I created a MainWindow class and added it to my little gui hello test application. Yes, you see those deletes commented out. I ran the application with those deletes in place fully expecting a crash. A layout is supposed to become the parent of all non-parented things directly added to it. I should have died with a double delete error message but I didn’t.

I honestly thought deleting m_widget would delete m_layout. It should have in my mind. My first run at this deleted only m_widget and I still had the big leaks from the movie. Adding deletion of the layout got rid of the big leaks the movie was previously creating.

Leakage summary

Leaking 4K doesn’t sound bad until you realize the program only ran for a few seconds. If this was a medical device where you had 512MEG or less, I would be worried if the leakage increased over time. Something left on long enough leaking 3-4K every few seconds will rather quickly run out of RAM.

Most of this stuff seems to be things I can do very little about.

XCB leakage

I know the developers of CopperSpice are currently working on XCB things as they recently released some code for XCB. Probably haven’t gotten around to running valgrind on things.

QAccessible loss one
QAccessible loss two

Part of me distinctly remembers QAccessible leakage in Qt 4.x . The other part of me worries about the QString8 I see there because that is new with CopperSpice. The 697 indirect loss repeated quite a few times in the file. A large part of that may well be tied to this 504 byte indirect loss.

It is these sub-1K leaks that really bite you. People ignore them because they aren’t large. What people fail to realize is that they tend to happen often. I’m not picking on just these leaks. I’m talking about sub-1K leaks in every project with every tool set. It’s the end of the project and you have already run out of time so they just get ignored. Now you fail your ten day run-time test because your sub-1K leak happens often enough to consume all RAM.

fontconfig leak

The fontconfig leak most likely has to be kicked upstream to the libexpat people. XML libraries and tools are notorious for leakage. Honestly I’m surprised this one is so small.

Why am I harping on memory leaks? Because I’m currently working on an editor. I don’t know about you, but on Linux machines I’ve been known to leave an editor up for over a week. I mean I save everything before I go home, but many times I never shut down. I want the cursor to be right where I left it in every file when I come in the next day. Sometimes I get called away and the thing is up for weeks on its own. Even on my machines with 16Gig or 24Gig of RAM just how long do you think it can last if it has a leak, especially in a background thread?

<Previous-part Next-part>

Compiling Qt 5.14 Under Msys2

I went down this road because I wanted to compile one simple test program so I could file a bug with GnuEmacs about how they don’t catch NumLock. This is a really old bug and they seem willing to let it rot until computers cease to exist. My test program was a KeyEvent example I stole from online then added support for NumLock. It took longer to scrape from the Internet than it did to test under Linux.

keypress.h

/*
 * stolen from http://programmingexamples.wikidot.com/qt-events
 * and modified for Qt 5.14
 */
#ifndef KEYPRESS_H
#define KEYPRESS_H

#include 

class QLabel;
class QVBoxLayout;

class KeyPress : public QWidget
{
    Q_OBJECT
public:
    KeyPress(QWidget *parent = 0);

protected:
    void keyPressEvent(QKeyEvent *);
    void keyReleaseEvent(QKeyEvent *);

private:
    QLabel *myLabel;
    QVBoxLayout *mainLayout;
};

#endif // KEYPRESS_H

keypress.cpp

/*
 * stolen from http://programmingexamples.wikidot.com/qt-events
 * and modified for Qt 5.14
 */
#include "keypress.h"


#include 
#include 
#include 

KeyPress::KeyPress(QWidget *parent) :
    QWidget(parent)
{
    myLabel = new QLabel("LABEL");
    mainLayout = new QVBoxLayout;
    mainLayout->addWidget(myLabel);
    setLayout(mainLayout);

}

void KeyPress::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Escape)
    {
        myLabel->setText("You pressed ESC");
    }

    if (event->key() == Qt::Key_NumLock)
    {
        myLabel->setText("NumLock was pressed!!!");
    }
}

void KeyPress::keyReleaseEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Escape)
    {
        myLabel->setText("You released ESC");
    }

    if (event->key() == Qt::Key_NumLock)
    {
        myLabel->setText("NumLock was released***");
    }

}

 

main.cpp

 

/*
 * stolen from http://programmingexamples.wikidot.com/qt-events
 * and modified for Qt 5.14
 */
#include 
#include "keypress.h"
#include 

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    KeyPress *keyPress = new KeyPress();
    keyPress->show();

    return a.exec();
}

It puts up a tiny little window. When you press the [Esc] key it displays a message. Another message when you release the key. I added a pair of messages for NumLock. The reason I went through all of this pain is I wanted to prove to these people that Qt can do it under Msys2/Windows and Linux so why can’t you just look at the code in Qt to see how they are intercepting key events. Since Qt is OpenSource, the code is there for all to see.

I had hoped there was a pre-compiled Qt development package in the Msys2 environment. After installing C/C++ compilers and a few other bits, my hopes were dashed. Adding insult to injury Qt currently only has a mkspec for 32-bit mingw.

Note: I don’t install the virus known as Windows on a bare machine as the primary OS. If I have to do anything with Windows I install it in a VM. Nobody should let that be a primary computer operating system. At least not on a computer they care about.

Open a terminal in Msys2 and type the following:

pacman -S base-devel git mercurial cvs wget p7zip
pacman -S perl ruby python2 mingw-w64-i686-toolchain mingw-w64-x86_64-toolchain

pacman -S clang
pacman -S mingw32/mingw-w64-i686-clang
pacman -S mingw64/mingw-w64-x86_64-clang
pacman -S mingw32/mingw-w64-i686-clang-analyzer
pacman -S mingw64/mingw-w64-x86_64-clang-analyzer
pacman -S mingw32/mingw-w64-i686-clang-tools-extra
pacman -S mingw64/mingw-w64-x86_64-clang-tools-extra
pacman -S mingw32/mingw-w64-i686-compiler-rt
pacman -S mingw64/mingw-w64-x86_64-compiler-rt
pacman -S mingw32/mingw-w64-i686-libblocksruntime
pacman -S mingw64/mingw-w64-x86_64-libblocksruntime

(The clang stuff is mostly to get the llvm stuff Qt is looking for.) Exit out of that terminal and open a new terminal and type

pacman -Syu

You will need to crash out of the terminal rather than typing exit once this is complete. Open a new terminal and type

pacman -Su

to complete installation. Depending on the quality of your Internet connection and the time of day, this could run a long time. You may want to add

 

 --disable-download-timeout

to your pacman command. It will take even longer because it won’t be skipping the slow/sucky mirrors, but it should not fail to download. The timeout feature of pacman seems to have been written by Twitter users. It fails with a message about no bytes within 10 seconds, but only takes 2-3 seconds to do that.

If something happens and pacman crashes (happened to me once) don’t panic. There is probably a dump file, but you don’t care about that. Wait a few seconds and retry the command. If you see something like this

rolan@DESKTOP-HTIGNPK MSYS ~
$ pacman -S mingw-w64-i686-qt5
error: failed to init transaction (unable to lock database)
error: could not lock database: File exists
  if you're sure a package manager is not already
  running, you can remove /var/lib/pacman/db.lck

Do this

rolan@DESKTOP-HTIGNPK MSYS ~
$ rm /var/lib/pacman/db.lck
rm: remove write-protected regular empty file '/var/lib/pacman/db.lck'? y

Theoretically you are now ready to configure Qt. I unzipped Qt in a shared directory so I had to cd here:

msys2 terminal prompt

Paste this in at the prompt.

windows2unix() { local pathPcs=() split pathTmp IFS=\;; read -ra split <<< "$*"; for pathTmp in "${split[@],}"; do pathPcs+=( "/${pathTmp//+([:\\])//}" ); done; echo "${pathPcs[*]}"; }; systemrootP=$(windows2unix "$SYSTEMROOT"); export PATH="$PWD/qtbase/bin:$PWD/gnuwin32/bin:/c/msys64/mingw64/bin:/c/msys64/usr/bin:$PATH"

You need more of the path than you typically get. That takes care of it.

 

./configure -opensource -confirm-license -release -skip qtwebengine -skip qtvirtualkeyboard -skip qtlocation -opengl desktop -qt-sqlite -qt-zlib -qt-libjpeg -qt-libpng -qt-freetype -qt-pcre -qt-harfbuzz -nomake examples -nomake tests -prefix /usr/local/bin/qt-5-14-1 -platform win32-g++ -silent 

Keep in mind I’m working with 5.14.1. You may want a different install directory. Neither qtvirtualkeyboard nor qtlocation will compile on msys2 so you definitely need to skip them. You might want to skip the 3d too if you have no plans on using it. In a VM assigned 2 CPUs and 8 Gig of RAM it took something like 6 hours to compile.

If configure spits up about some missing dependency, don’t panic. Simply find the dependency and install it. Remember, you need the 32-bit version for this build of Qt, but you might as well install both the 32 & 64-bit versions if you have the disk space.

Before running configure again you should delete these files.

$ rm config.summary
$ rm .qmake.cache
$ rm .qmake.stash
$ rm config.cache
$ rm config.log

A nice clean configure will end like this without any messages about missing things or errors.

Note: No wayland-egl support detected. Cross-toolkit compatibility disabled.

Qt is now configured for building. Just run 'make'.
Once everything is built, you must run 'make install'.
Qt will be installed into 'C:\msys64\usr\local\bin\qt-5-14-1'.

Prior to reconfiguration, make sure you remove any leftovers from
the previous build.

Note: If your build fails and you decide to configure differently, you need to run “make clean” before you delete the files mentioned above.

mingw32-make -j 4

I only had 2 CPUs assigned to this VM but I still used 4. Please note that you need to use mingw32-make. Hours later, if your build seems to go fine

mingw32-make install

If all goes well, not a given since make install seems to build a bunch of stuff the actual make skipped, you can exit that terminal window. Don’t try to use it for anything else because you’ve jacked with the path.

path after install

Note that Msys2 automatically puts /usr/local/bin in the PATH. It’s just not polite though.

msys2 path image

You have to edit your .bash_profile

bash_profile image

Things get better after you close your terminal window and open another.

image after qmake

after make image

msys2 success image

It runs really really slow launched from the command line like that. (Probably doesn’t help being in a 2 CPU VM!) Yes, I could probably fix my Windows environment to make things better, but that wasn’t the point of this exercise. There for everyone to see is the release message from NumLock having been pressed.

 

MOC Parse error at “std”

You’re being a good little geek. You need to write a Qt program for both 32-bit and 64-bit platforms so you are using multiple VMs and keeping the repository on a shared drive. Everything compiles clean and runs great on the 32-bit VM. You boot the 64-bit VM, mount the shared drive and bam!

usr/include/c++/7/bits/stl_relops.:67: Parse error at "std"

Sucks to be you!

Searching the Web leads you to bug reports like this one. Good luck with that rabbit hole! More poking around leads to various things.

developer@developer-U18-04-64-bit-dev-VirtualBox:/mnt/share/git_repo/ipos$ ls /usr/include/x86_64-linux-gnu/c++/7/bits/c++config.h
/usr/include/x86_64-linux-gnu/c++/7/bits/c++config.h

You try hand installing QtCreator and re-starting the 64-bit VM which thankfully doesn’t solve the problem, because if it did you would _really_ be lost. In desperation you add the following to your .pro file knowing it is a hack you will need to trap on the 32-bit side.

INCLUDEPATH += /usr/include/x86_64-linux-gnu/c++/7/bits

No luck! And Web searches are of no use. How could this go so sideways?

Well my little geek, you got screwed with your pants on from afar. Some misguided powers that be high up in the Qt world decided to add a file to the Qt compile process and basically said nothing about it.

-rwxrwxrwx 1 root root 739 Oct 26 15:01 .qmake.stash

That hidden file needs to be blasted each time you build if you use a shared drive or someone accidentally added it to your source repo. It is automatically generated and stashes library paths. If you are using a shared drive to build and test your repo for multiple targets, the first step in your build process must be to nuke it. Then you will never see this error, your build will just take a touch longer.

For more insight about the art of software development read my new book “The Minimum You Need to Know About the Phallus of AGILE