Saturday, November 26, 2011

Getting things done with qmake

I frequently deal with multiple operating systems on various machines and so make efforts to write portable code and use portable third-party libraries where possible.  On some machines I have administrator access while on others I am a lowly user.  How to consistently configure and build projects across all systems became an issue.

Without much forethought, I began to use qmake from NVidia's Qt toolkit.  I did this originally because I was using Qt to build cross-platform GUIs for code, but continue to do so because it works well and is dead easy. There are other cross-platform build systems out there such as CMake, SCONS, and so on, but I've yet to find one that gives me a compelling reason to switch from qmake.

That said, the process that I went through to finally arrive at this process was not straightforward.  This post is intended to roughly document how I set up my machines to work cleanly and easily with qmake. Ultimately this boils down to:
  • Use ONLY portable third-party libraries
  • Create consistently-named qmake include files for each library on each system
  • Keep ALL those include files in a single directory, ideally with the source-tree of the library
  • Define a consistently-named environment variable on all machines that points to the directory
As an example, on OS-X, I have a directory Code/ThirdParty, pointed to by the environment variable $ThirdPartyDir which gives the full path to Code/ThirdParty.  The contents of this directory are:

The *.pri files are the qmake include files, while the directories hold the library source trees. Some libraries, like CGAL, automatically install themselves to a different location on the machine, such as /usr/local.  That doesn't matter, the project include file still goes in the directory pointed to by $ThirdPartyDir.  On Windows, I have another directory ThirdPartyDir, located at a completely different path, pointed to by the environment variable %ThirdPartyDir%, with the same sub-directories and versions of the *.pri files modified to work for Windows.

The *.pri files themselves handle adding linker flags, header search paths and any preprocessor definitions needed to the project.  They look like incomplete qmake project files, as seen by the excerpt of my vtk.pri file:

INCLUDEPATH += "/usr/local/include/vtk-5.8"
LIBS += -L/usr/local/lib/vtk-5.8
LIBS += -lvtkalglib \
        -lvtkCharts \
        -lvtkCommon \
        -lvtkGraphics \
        -lvtkDICOMParser \
        -lvtkexoIIc \
Now any project that needs vtk can simply include the qmake include file into it's project file.  Provided the include file exists and is correct, all the include paths, linker flags and preprocessor definitions will be set up correctly, as in the following project file:

QT       += opengl
CONfIG   += console debug_and_release
TARGET   = camera_model

!include( $$(ThirdPartyDir)/eigen.pri )
!include( $$(ThirdPartyDir)/cimg.pri )
!include( $$(ThirdPartyDir)/vtk.pri )

mac {
    CONFIG -= app_bundle
    MOC_DIR = build

INCLUDEPATH += include
HEADERS += include/camera.h
SOURCES += src/main.cpp \

This project file is now completely portable, able to be built on Windows, OS-X and Linux without change.  The include files themselves eigen.pri, cimg.pri and vtk.pri may differ between systems as necessitated by versions, access permissions and install locations, but the project itself is consistent.

This process is ultimately similar to how CMake is intended to function, however I have wasted hours trying to get CMake find scripts to function, often without success.  By just creating these include files whenever I use third-party code, I've found that much of the frustration of cross-platform development just goes away.

No comments: