Tup

From Qt Wiki
Revision as of 03:20, 18 May 2017 by Henrik Jensen (talk | contribs) (Sometimes the <code> tag needs whitespace before closing </code> to not mangle the whole page it seems !!!?)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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:

QWidget Example

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.