Tuesday, February 16, 2010

Extending qmake builds: static libraries and automated dependencies

Adding to the previous post, in which an app is automatically rebuilt if a source file changed in the library, here is a method of simplifying and automating that dependency. Adding a second library to the project, the directory structure looks like this:

TrialSystem/TrialSystem.pro
TrialSystem/qmake_postcommon.pri
TrialSystem/trialApp/trialApp.pro
TrialSystem/trialLibA/trialLibA.pro
TrialSystem/trialLibB/trialLibB.pro

First some requirements:
  • The name of the library directory "trialLibA" must match the name of its library (on Windows using mingw, libtrialLibA.a).
  • The header(s) for the library must be in the directory along with the library's qmake project ".pro" file.
  • The library must be statically linked.
  • If you don't want to use this technique for other libraries (with oddly named libraries or some other directory hierarchy), then simply don't add that library to the LIBRARIES variable mentioned below. You can still use this technique for other libs that conform to these limitations.
First, we look at the new trialApp.pro:
include(../qmake_common.pri)

TARGET = trialApp
TEMPLATE = app
CONFIG += link_prl

LIBRARIES = ../trialLibA ../trialLibB

SOURCES += main.cpp\
MainWindow.cpp
HEADERS += MainWindow.h
FORMS += MainWindow.ui

include(../qmake_postcommon.pri)
(Ignore, for this post, the placeholder include file "qmake_common.pri," which is empty for now)

Instead of specifying INCLUDEPATH, the path to the library and the name of the library in LIBS, along with the new "PRE_TARGETDEPENDS," there is simply a new variable named "LIBRARIES." I've added this variable, it is not a qmake reserved name.

The simplification of this file is enabled through the inclusion of "qmake_postcommon.pri" (Again, thanks for eating the indentation, Mr. Blogspot).

win32 {
LIBPREFIX = lib
LIBSUFFIX = .a

# http://doc.trolltech.com/4.6/qmake-function-reference.html#config-config
CONFIG(debug, debug|release) {
LIBDIR = debug
} else {
LIBDIR = release
}
}

message("----")
!isEmpty(LIBRARIES) {
for(libpath, LIBRARIES) {
libbase = $$basename(libpath)
INCLUDEPATH += $$libpath
LIBS += -L$${libpath}/$${LIBDIR}
LIBS += -l$$libbase
PRE_TARGETDEPS += $${libpath}/$${LIBDIR}/$${LIBPREFIX}$${libbase}$${LIBSUFFIX}
}
message($$INCLUDEPATH)
message($$LIBS)
message($$PRE_TARGETDEPS)
}

This include file clearly only works for Windows using mingw. I will be porting this to linux as well.

The first section of the file in the "win32" scope defines the convention for the naming and storing of the library. This is necessary for setting PRE_TARGETDEPS for proper build dependencies.

the second section inside !isempty(LIBRARIES) sets the qmake variables necessary for finding the libraries, setting the include path and handling the dependencies. The loop simply walks through the listed libraries and sets the proper values.

With this configuration, changing a source file in either trialLibA or trialLibB causes the entire system to build correctly, and the .pro files for each of the projects is radically simplified and enabled for cross-platform development.

Interestingly, for Windows, I've added a reference from trialLibA to trialLibB. trialLibB is also called from the app trialApp. With the mingw linker, all the references are handled correctly without error.

Left-click here to download a copy of the project from skydrive (the popup makes it look like a link to a zip file, but it's not. You have to click through).

Friday, February 12, 2010

Qt Library Dependencies -- rebuilding dependencies with qmake

Left-click to Download the Sample Project

I modularize my projects into multiple libraries. The best reason is Separation of Concerns, but it also allows each library to link with its own unit test.

There's an app, then there are several libraries that the app depends upon.

I'll organize my system as such:
TrialSystem/TrialSystem.pro
TrialSystem/trialApp/trialApp.pro
TrialSystem/trialLibA/trialLibA.pro
Now, trialApp depends on trialLibA, therefore if anything changes in trialLibA, I want the build system to first rebuild trialLibA then link that newly built library into trialApp. (I'm building static libraries).

One solution to this problem uses the "subdirs" template for the TrialSystem.pro file. This solution requires the entire system to be in the same directory hierarchy, and it precludes parallel builds (if possible, the build system spins multiple threads to spread the load across multiple processors).

-- This is not possible through the menus of Qt Creator. --

First, create System.pro. It should have this content:
TEMPLATE = subdirs
SUBDIRS = trialLibA trialApp
CONFIG += ordered
This qmake project file builds the subdirectories "trialLibA" and "app" in that order. Now, if anything has changed in the library, the library is built first, then the app is relinked.

One nice feature of this setup is that when Qt Creator opens System.pro, it automatically shows the "app" and "libA" projects in the Projects window.

The project file trialLibA.pro has this content. This is a pretty straightforward static library:

TARGET = trialLibA
TEMPLATE = lib
CONFIG += staticlib
CONFIG += create_prl
SOURCES += TrialLibA.cpp
HEADERS += TrialLibA.h
The application's project file, trialApp.pro has this content. This is a mingw build under Windows (thanks for the formatting, Mr. Blogger.com):
TARGET = trialApp
TEMPLATE = app

win32 {
CONFIG(debug, debug|release) {
LIBS += -L../trialLibA/debug
PRE_TARGETDEPS += ../trialLibA/debug/libtrialLibA.a
} else {
LIBS += -L../trialLibA/release
PRE_TARGETDEPS += ../trialLibA/release/libtrialLibA.a
}
}

INCLUDEPATH += ../trialLibA
LIBS += -ltrialLibA

SOURCES += main.cpp\
MainWindow.cpp
HEADERS += MainWindow.h
FORMS += MainWindow.ui

Note the addition of PRE_TARGETDEPS. This unfortunate platform-specific code (it specifies the precise name of the library along with its path) forces trialApp to relink if the library changes.

Now, it's possible to change something down in trialLibA.cpp, hit "F5" and have the application run correctly in debug mode.

Now, these acrobatics shouldn't be necessary if qmake supported proper dependency trees automatically.

http://lists.trolltech.com/qt-interest/2007-03/thread00794-0.html

http://www.qtcentre.org/threads/28049-qmake-Project-file-has-both-debug-and-release-conditions-true

Sunday, January 31, 2010

Cleaning Qt before rebuild on Windows.

I ran into this problem when rebuilding Qt 2010.01, but it's been around for a while.

now, to clean the system before a rebuilt:

mingw32-make clean
mingw32-make confclean
del /S *.tmp
configure -shared
mingw32-make sub-src

Wednesday, January 27, 2010

Qt 4.6 (2009.05) fails to build: static mingw on Windows

NOTE: This build problem is fixed in 2010.01 (4.6.1). I will be testing this build today.

NOTE: The build is fixed, but I've filed a bug against Widgets due to QLabel unable to setPixmap(). I'd like to have a statically-built app (for general-purpose tools I can share with my team as exe's). Perhaps I'll move back to Qt 5.

Qt Fails to build on 4.6 (2009.05) with configure -static.

g++ -c -g -frtti -fexceptions -mthreads -Wall -DUNICODE -DQT_LARGEFILE_SUPPORT -DPHONON_MAKE_QT_ONLY_BACKEND -DQT_STATIC
PLUGIN -DQT_PLUGIN -DQT_PHONON_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_THREAD_SUPPORT -I"..\..\..\..\include\QtCore" -I"..\.
.\..\..\include\QtGui" -I"..\..\..\..\include\phonon" -I"..\..\..\..\include" -I"..\..\..\..\include\ActiveQt" -I"..\..\
..\..\include\phonon_compat\phonon" -I"..\..\..\..\include\phonon_compat" -I"..\..\..\..\include\phonon\Phonon" -I"tmp\m
oc\debug_static" -I"..\..\..\..\mkspecs\win32-g++" -o tmp\obj\debug_static\backend.o ..\..\..\3rdparty\phonon\ds9\backen
d.cpp
..\..\..\3rdparty\phonon\ds9\backend.cpp: In static member function 'static QMutex* Phonon::DS9::Backend::directShowMute
x()':
..\..\..\3rdparty\phonon\ds9\backend.cpp:69: error: 'qt_plugin_instance' was not declared in this scope
mingw32-make[4]: *** [tmp/obj/debug_static/backend.o] Error 1
mingw32-make[4]: Leaving directory `C:/Qt/2009.05/qt/src/plugins/phonon/ds9'
mingw32-make[3]: *** [debug-all] Error 2
mingw32-make[3]: Leaving directory `C:/Qt/2009.05/qt/src/plugins/phonon/ds9'
mingw32-make[2]: *** [sub-ds9-make_default] Error 2
mingw32-make[2]: Leaving directory `C:/Qt/2009.05/qt/src/plugins/phonon'
mingw32-make[1]: *** [sub-phonon-make_default] Error 2
mingw32-make[1]: Leaving directory `C:/Qt/2009.05/qt/src/plugins'
mingw32-make: *** [sub-plugins-sub_src_target_ordered] Error 2


Simply leave out Phonon (the audio component) because it is not allowed with static linking mode. (This is a bug in the build system). You'd think they'd at least build all the configurations before release...

So, to build Qt 4.6 (2009.05) for a statically-linked application, cd to your qt directory (e.g. C:\Qt\2009.05\qt) and simply build it without phonon.
mingw32-make distclean
config -confclean
configure -static -no-phonon -no-phonon-backend
mingw32-make sub-src
This built the Qt system, and my app ran successfully after I built it in Qt Creator. Unfortunately, now the QLabel refuses to setPixmap(). My image labels don't show on the GUI. I'll be abandoning this train of thought soon, either by downgrading to 4.5.3 or by staying with the dynamically linked 4.6.

My system is a Dell Xeon E5540 quad-core 2.53GHZ. Windows XP. 12 Gigs RAM. 2TB RAID 5. Hoohoohaha.

Thursday, January 14, 2010

Qt 4.5.3 for Mac uninstall required before installing Qt 4.6

Here's something pretty subtle that I hadn't picked up. In the install notes, it tells how to perform the uninstall:

"You can later run the uninstall-qt.py script to uninstall the binary package."

sudo /Developer/Tools/uninstall-qt.py

I had to do this prior to installing Qt 4.6 over Qt 5.3 because Qt decided to store the app into /Developer/Applications/Qt/QtCreator without (of course) removing the old one.

In Windows, they install it directly into the C: directory, into a subdir with the version number. I'm wondering how (if?) they'll support multiple active versions on the Mac.

Wednesday, January 13, 2010

Extracting an item from a QStandardItemModel

Trial and error led me to this. I have a QListView and a QStandardItemModel. I select one of the items in the View and would like to retrieve it for use.

Need to know which row index was clicked (be sure and set the QListView selectingBehavior to "selectRows," otherwise you may get just a single item within the row). That "selectionModel()" is a tricky bit that wasn't obvious from the docs.

QModelIndexList indices = ui->treeView->selectionModel()->selectedRows();

The QListView is setup so that only a single row can be selected at a time. Verfiy that at least one row was chosen, then determine the correct row index:

if ( indices.count() == 1 )
{
  QModelIndex idx = indices[0];
  int row = idx.row();

Retrieve the model from the view:

QStandardItemModel *model =
  dynamic_cast < QStandardItemModel* >(ui->treeView->model());

QStandardItemModel allows you to retrieve a QStandardItem using integers for row and column (I got wrapped around the axle about QModelIndex from model->data(), but the createIndex() method was protected inside QStandardItemModel. We don't need that, just the integer indices)

QStandardItem *firstColumnItem = model->item(row, 0);

Internally, the object is stored as a QVariant. My two columns contain QString and QDateTime. FYI I used Qt::DisplayRole when inserting the objects into the model.

QVariant
  firstColumnVariant(firstColumnItem->data(Qt::DisplayRole));
QString
  firstColumnString(firstColumnVariant.toString());

Next, I got the second column with some shorthand:
QVariant
  dateTimeVariant(model->item(row,1) ->data(Qt::DisplayRole));
QDateTime
  dateTime(dateTimeVariant.toDateTime());

And popped it up in a MessageBox:

QMessageBox(QMessageBox::Information, firstColumnString, dateTime).exec();

QTreeView (QAbstractItemView) and PageUp/PageDown

Just a note on QTreeView and PageUp/PageDown. I'm developing an app designed for fingers on a big screen, and need a button for PageUp/PageDown rather than using the scrollbar.

QTreeView.autoScroll must be set to "true" to enable PageUp/PageDown key commands to move the list.