Tup
by Simon Wilper
Introduction
According to the homepage Tup is a file-based build system for Linux, OSX, and Windows. It inputs a list of file changes and a directed acyclic graph (DAG), then processes the DAG to execute the appropriate commands required to update dependent files. Updates are performed with very little overhead since tup implements powerful build algorithms to avoid doing unnecessary work. This means you can stay focused on your project rather than on your build system.
This article is going to show how tup can be used to build Qt GUI projects. Creating a project based on tup primarily evolves around creating and editing a Tupfile followed by some tup commands.
Lets assume the following small Qt project, a QtWidget containing a label, nothing fancy:
main.cpp
#include "mainwindow.hpp"
void MainWindow::initialize() {
setWindowTitle("Small QWidget Example");
QWidget *wCmdBar = new QWidget(this);
QHBoxLayout *layoutCmdBar = new QHBoxLayout;
wCmdBar->setLayout(layoutCmdBar);
mBtnClose = new QPushButton("Close",this);
layoutCmdBar->addStretch();
layoutCmdBar->addWidget(mBtnClose);
QVBoxLayout *layoutCentral = new QVBoxLayout;
mLabelInfo = new QLabel( "This is a label in a QWidget", this );
layoutCentral->addWidget(mLabelInfo);
layoutCentral->addWidget(wCmdBar);
setLayout(layoutCentral);
connect( mBtnClose, SIGNAL(clicked()), this, SLOT(handleQuit()) );
}
void MainWindow::handleQuit() {
if ( QMessageBox::question(this, "Question", "Really quit?") == QMessageBox::Yes ) {
qApp->quit();
}
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
MainWindow w;
w.initialize();
w.show();
return app.exec();
}
mainwindow.hpp
#ifndef __mainwindow_hpp
#define __mainwindow_hpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QMessageBox>
class MainWindow : public QWidget {
Q_OBJECT
public:
void initialize();
private slots:
void handleQuit();
private:
QPushButton *mBtnClose;
QLabel *mLabelInfo;
};
#endif
When the user clicks the close button a QMessageBox shall ask for confirmation. A slot handle_quit() is responsible for that.
Directory Structure
For this howto the following directory structure is assumed:
. ├── src │ ├── main.cpp │ └── mainwindow.hpp └── Tupfile
The Tupfile
Analogous to a Makefile in a make project for tup we need a Tupfile. Unlike in cmake, qmake or qbs where things like moc files get generated automatically we need to take some extra steps in tup but nothing too difficult.
Create the Tupfile in the root directory of your project and start with some variables for the Qt installation location:
# Change the base directory to your Qt installation
qt_base=/opt/qt5
qt_include_base = $(qt_base)/include
qt_lib = $(qt_base)/lib
I built my own Qt5.8 and installed it to /opt/qt5.
# Allow Tup to search the src directory
preload src
Tup needs clearance for each single directory it possibly can find source or header files. In our example we only have two files in the src subdirectory so we just need to preload this one.
Next we need some tup rules for creating the moc output files. As soon you need e.g. the signal slot system of Qt or any other functionality of the Qt Meta Object system you need to generate a further cpp file with moc:
# Create rules for the MOC by grepping cpp files for Q_OBJECT
run find src/ -name '*.hpp' | xargs grep -H Q_OBJECT |\
awk -F: '{print ": " $1 " |> moc -o %f_moc.cpp %f |> %f_moc.cpp"}'
This line does the following:
- use find to find all header files (*.hpp)
- pipe the results to xargs using grep to find the string Q_OBJECT. We want only these headers to be processed by moc
- use awk to print the rule
The result should look like this:
: src/mainwindow.hpp |> moc -o %f_moc.cpp %f |> %f_moc.cpp
The -H commandline parameter passed to grep ensures that the output always includes the filename so that awk can easily split by colon (-F:) and print the rule.
Now for all headers found below src/ a rule of the form above is created on the fly so that tup will execute moc for each header.
Next we use a foreach rule that compiles all cpp files below the source directory. I used most of the g++ commandline parameters I found in the Makefile generated by qmake. This rule will create the obj directory populated with all object files, in this case main.o and mainwindow.hpp_moc.o
# Compile CPP files
:foreach\
src/*.cpp\
|> g++ -c -pipe -O2 -std=c++1y -Wall -W -D_REENTRANT -fPIC\
-DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB\
-Isrc\
-I$(qt_include_base)\
-I$(qt_include_base)/QtWidgets\
-I$(qt_include_base)/QtGui\
-I$(qt_include_base)/QtCore\
-I$(qt_lib)/mkspecs/linux-g++\
-o %o\
%f\
|> obj/%B.o
Finally the linker rule:
: obj/*.o |> g++ -Wl,-O2 -Wl,-s -Wl,-rpath,$(qt_lib)\
-L$(qt_lib)\
-lQt5Core\
-lQt5Gui\
-lQt5Widgets\
-lGL\
-lpthread\
-o %o\
%f\
|> bin/qttest
Here it is important to pass the rpath parameter to the linker so that the Qt libraries can be found. Ldd will tell you that, otherwise you have to LD_PRELOAD them.
> ldd bin/qttest | grep Qt libQt5Core.so.5 => /opt/qt5/lib/libQt5Core.so.5 (0xb725f000) libQt5Gui.so.5 => /opt/qt5/lib/libQt5Gui.so.5 (0xb6c50000) libQt5Widgets.so.5 => /opt/qt5/lib/libQt5Widgets.so.5 (0xb6566000)
The -s parameter will strip the binary:
> file bin/qttest bin/qttest: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=4ad09f1e8f4c977900ac2dd468bab8e5382b, stripped
Running tup
Tup makes use of an internal SQLite database in order to figure out what to recompile. In order to create these initial files run
> tup init .tup repository initialized.
That will create a .tup directory
> tree .tup .tup ├── db ├── object ├── shared └── tri 0 directories, 4 files
Now run your build:
> tup [ tup ] [0.000s] Scanning filesystem... [ tup ] [0.010s] Reading in new environment variables... [ tup ] [0.010s] Parsing Tupfiles... 1) [0.000s] src 2) [0.028s] . [ ] 100% [ tup ] [0.042s] No files to delete. [ tup ] [0.042s] Generating .gitignore files... [ tup ] [0.046s] Executing Commands... 1) [0.033s] moc -o src/mainwindow.hpp_moc.cpp src/mainwindow.hpp 2) [0.049s] g++ -c -pipe -O2 -std=c++1y [...] -o obj/main.o src/main.cpp 3) [0.041s] g++ -c -pipe -O2 -std=c++1y [...] -o obj/mainwindow.hpp_moc.o src/mainwindow.hpp_moc.cpp 4) [0.248s] g++ -Wl,-O2 -Wl,-s -Wl,-rpath,/home/sxw/qt5/lib [...] -o bin/qttest obj/main.o obj/mainwindow.hpp_moc.o [ ] 100% [ tup ] [0.376s] Updated.