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).

1 comment:

  1. Thanks,

    I've been banging my head against the wall for a few days trying to figure out how to trigger a relink in the exe when a static library changes.

    Cristian

    ReplyDelete