25 March 2015

Step by Step: Developing an Application using CMAKE, QT and Boost.



Introduction

This tutorial will show you how to create a CMake project that associated with Boost and QT library.  For better understanding, please download my completed tutorial project from Download Complete Project
The source project use Qwidget, Qml, boost Signal2 and boost thread. It  also contains .UI and .RCC file. The app will be built on Windows and Kubuntu Linux (the KDE version of Ubuntu) using MSVC and GCC.

Background

C++ is available on many platforms. So are the QT and Boost library. Unlike C#, C++ compiler generates the native binary file which cannot be run on other OS. Therefore, C++ source code has to be complied on certain compiler/OS in order to create a valid application. This is seemed to be an overhead, but the performance of native application is worth the trouble. See this article.

CMake

CMake is a free opensource tool for managing the build system. CMake will read your project infomation from CMakeList.txt and then, generate native build project file (ie. .sln , MakeFile).

QT

QT is a GUI and aplication framework. QT has their own specific compiling steps. Fortunately, CMake already has a feature to handle that. Such features are AUTO_MOC, AUTO_UIC and AUTO_RCC.

Boost

A programing language without framework is powerless. The standard C++11 library is still small compare to other frameworks such as .NET. Thus, we need Boost library which contains almost everything you need for general work.




Installing CMake, Boost and QT

CMake, Boost and QT are opensource. You can choose to either build from source or install from binaries. This session will show you how to install CMake and QT from binary distribution, while Boost will be built from source code.
  • Installing CMake

    Obtains the CMake installer from CMake download link . Then, run the installer wizard.

     For Kubuntu Linux, modify the file permissions and run with these commands.

    $ chmod a+x cmake-3.2.1-Linux-i386.sh
    $ ./cmake-3.2.1-Linux-i386.sh

  • Installing QT

    Obtains the QT installer from QT download link .
    On that page, I personally prefer choosing the offline installer.  Click the “view all download“ link to show the offline installers.

    Make sure that you have downloaded the correct version and architecture of complier. For example, if you plan to build Boost by visual C++ 2013 32 bit, choose Qt 5.4.1 for Windows 32-bit (VS 2013, OpenGL, 695 MB).  For the install location, please read "Managing Source Code and Build Information".

    For Kubuntu Linux, modify the file permissions and run the .run file.

    $ chmod a+x qt-opensource-linux-x86-5.4.1.run
    $ ./qt-opensource-linux-x86-5.4.1.run

  • Installing Boost

    First, obtains the source code from Boost download link . Then,  extract the zip (or tar) file. Open command prompt, and cd to the extracted folder. Inside that directory (boost_1_57_0.7z\boost_1_57_0\), run these commands
    1. Run bootstrap.bat (bootstrap.sh for Linux)

      C:\boost_1_57_0\boost_1_57_0>bootstrap

    1. Run b2.exe (b2 for Linux). See Table 1 for the detail of command arguments.

      C:\boost_1_57_0\boost_1_57_0>b2 toolset=msvc-12.0 link=static install address-model=32 --build-type=complete --build-dir=C:\Boost\Build --prefix=C:\Boost



toolset=msvc-12.0 Use the visual C++ 12.0 2013 complier  (For Linux, use toolset=gcc-4.8).
link=static Build as static lib.
Address-model=32 Set binarie's architecture to 32 bit. Use Address-model=64 for 64 bit.
Install This argument will copy the output file to the prefix folder.
--build-type=complete Build both debug and release version.
--build-dir=C:\Boost\Build Directory for object files and caches (just an intermediate files).
--prefix=C:\Boost The location of “output” library. In this tutorial, I will use --prefix=C:\Boost64 for 64 bit build. For Linux, I will set prefix to --prefix=/home/apivan/boost.To customize this please see my CMakeList.txt.
Table 1 Detail of command arguments


Please notice the difference between double dash “--” and no dash. You possibly have some built failure on python-related libs. You can simply ignore it, or just installs python.

For Kubuntu Linux, make sure that you have OpenGL installed.
$ sudo apt-get install freeglut3
$ sudo apt-get install freeglut3-dev
$ sudo apt-get install libgl1-mesa-dev



Managing Source Code and Build Information

All the source code will be located here in the source directory. In other word, only files in this directory should be pushed into your version control.You can skip this session if you only interested in building the tutorial project.

Below are the files in this tutorial project.

SimpleApp
|-CMakeLists.txt
|-main.cpp
|-mainwindow.h
|-Resources.qrc
|-SimpleQml.qml
|-mainwindow.cpp
|-mainwindow.ui


Let observe the CMakeList.txt file.

CMAKE_MINIMUM_REQUIRED(
  VERSION 2.8.11)

PROJECT(SimpleApp)


 
SET(CMAKE_INCLUDE_CURRENT_DIR ON)   

# Check the environment of build system
IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU") 

# Enable C++11 on GCC (the MSVC will enable C++11 by default.
# The syntax below will append -std=c++11 to the existing
# CMAKE_CXX_FLAGS.
    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 
    
ENDIF()

# Check the architecture of complier. 
# Then, add Boolean value to ARCH_32BIT and ARCH_64BIT.
SET(ARCH_32BIT ${CMAKE_SIZEOF_VOID_P} EQUAL 4) 
SET(ARCH_64BIT ${CMAKE_SIZEOF_VOID_P} EQUAL 8) 

As you can see, the code is very straight forward. The architecture of complier can be checked by reading the size of void pointer.

The code below manages QT lib. It will check the current build environment, and store that value to QT_INSTALLED_PATH.  That path is the path that you was asked by QT's installer wizard. To change the QT installed path, change the value in QT_INSTALLED_PATH.

IF(WIN32 AND ${ARCH_32BIT})

 SET(QT_INSTALLED_PATH "C:/QtMSVCX86/Qt5.4.1/5.4/msvc2013_opengl")

ELSEIF(WIN32 AND ${ARCH_64BIT})

 SET(QT_INSTALLED_PATH 
"C:/QtMSVCX64/Qt5.4.1/5.4/msvc2013_64_opengl")

ELSEIF(UNIX  AND NOT MINGW AND ${ARCH_32BIT})

 SET(QT_INSTALLED_PATH "/opt/Qt5.4.1/5.4/gcc/" )

ELSEIF(UNIX  AND NOT MINGW AND ${ARCH_64BIT})

 SET(QT_INSTALLED_PATH "/opt/Qt5.4.1/5.4/gcc_64/" )
    
ENDIF()

# Add path environment for CMake. 
SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_INSTALLED_PATH} 
  CACHE PATH "")

# These code will enable the auto MOC, UIC and RCC feature.
# See http://www.cmake.org/cmake/help/v3.0/manual/cmake-qt.7.html
SET(CMAKE_AUTOMOC ON)
SET(CMAKE_AUTOUIC ON)
SET(CMAKE_AUTORCC ON)

# These code will add packages that was used in the source files.
FIND_PACKAGE(Qt5Widgets) 
FIND_PACKAGE(Qt5Qml)
FIND_PACKAGE(Qt5Quick)


Note that the WIN32 refer to both Windows 32 bit and Windows 64 bit.
The code below manages BOOST lib.

# Add search with prefix "lib". 
# On windows, b2 generate static lib with that prefix
# such as libboost_signals-vc120-s-1_57.lib.
SET(CMAKE_FIND_LIBRARY_PREFIXES lib)

# Disable Boost_NO_BOOST_CMAKE. 
# This is to remove "BOOST_DIR not found". 
SET(Boost_NO_BOOST_CMAKE  ON)
IF( WIN32 AND ${ARCH_32BIT})

    SET(BOOST_INSTALLED_PATH   C:/Boost)
    
# ... Code skipped ...

ENDIF()

SET(BOOST_ROOT         ${BOOST_INSTALLED_PATH})
SET(BOOST_LIBRARYDIR   ${BOOST_INSTALLED_PATH}/lib)
SET(BOOST_INCLUDE_DIR  ${BOOST_INSTALLED_PATH}/include)

SET(BOOST_USE_STATIC_LIBS        ON)
SET(BOOST_USE_MULTITHREADED      ON)

# Use default runtime-link (thing like vc redist ..)
SET(BOOST_USE_STATIC_RUNTIME    OFF)

FIND_PACKAGE(Boost 1.57.0 COMPONENTS signals system thread REQUIRED)

INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR})
LINK_DIRECTORIES(    ${BOOST_LIBRARYDIR})



Similar to that of QT, the code is very straight forward. Make sure that you have installed boost to the correct path. Please check “b2” argument and BOOST_INSTALLED_PATH.

The code below will compile and link the source codes to the exe file.

ADD_EXECUTABLE(SimpleApp main.cpp mainwindow.cpp Resources.qrc)

TARGET_LINK_LIBRARIES( SimpleApp
 Qt5::Widgets   Qt5::Qml
 Qt5::Quick     ${Boost_LIBRARIES})


And, here are the codes in mainwindows.cpp and .h file

#pragma once
#include <QMainWindow>
#include <QtQuick/QQuickView>

#include <boost/signals2/signal.hpp>

#include <memory>

class MainWindow : public QMainWindow
{
  Q_OBJECT 

// ... Code skipped ...

private:

  std::unique_ptr<Ui::MainWindow>  m_Ui;
  std::unique_ptr<QQuickView>      m_QmlView;
  boost::signals2::signal<void()>  m_BoostSignal;

};
.Cpp

// ... Code inside constrctor 
m_BoostSignal.connect(
  [this](){

  boost::future<QString> f = boost::async(
    [](){return QString("Hello from  boost future");});

  this->m_Ui->plainTextEdit->appendPlainText (f.get());
});

connect(m_Ui->pushButton, &QPushButton::clicked,
  [this](){
     m_BoostSignal();
  });

connect(m_QmlView->rootObject(),
    SIGNAL(qmlSignal(QString)),
  
  this,
   SLOT(OnQmlSignaled()));

 

Generating the Native Build Project

In this sesssion, I will show you how generate the native build project defined in CMakeList.txt.

  1. Open the “cmake-gui” then click “Browse Source” to fill “Where is the source code” field. Then, click “Browse build” to fill “Where to build the binaries” as shown in Figure 1. 
  2. Click “configure” and select your compiler. In this tutorial, external libraries (Boost and QT) binaries are “MSVC-12.0” on windows and “GCC” on Linux. Thus, to make this tutorial working. For Windows, please select “Visual Studio 12 2013”. For Linux, please select “Unix Makefiles” as shown in Figure 2.

Figure 1 Filling the “Where is the source code” and “Where to build the binaries” on  CMake gui

Figure 2 Selecting the native build project generator


  1. Click “Configure” again, and then click “Generate”. If everything was setup correctly, you should see the message like these. See Figure 3.
    Figure 3 Successfully generated native build project
For those who have to build on the command line, create a new folder and cd to that folder. That folder is the “Where to build the binaries” folder. Then, type
>cmake -G "Visual Studio 12 2013" .\SimpleApp

 

Building the Generated Project

Open the command prompt, cd to the “Where to build the binaries” directory.
For windows, Run the vcvarshell.bat to prepare environment for MSbuild. Then, build the project. See comands below.

>"C:\Program Files\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"
> msbuild /P:Configuration=Release ALL_BUILD.vcxproj

To run the built app, add Qt’s Dll to the environment and run SimpleApp.exe. See commands below. If everything is correct, you should see the app running as shown in Figure 4.
> set path=%path%;C:\QtMSVCX86\Qt5.4.1\5.4\msvc2013_opengl\bin
> Release\SimpleApp.exe

For Linux, run
$ make

To run the built app,
$ export PATH=$PATH:/opt/Qt5.4.1/5.4/gcc/bin/
$ ./SimpleApp
Figure 4 Running the App on windows


Figure 5 Running the app on Kubuntu Linux



Developing and Debugging under IDE

For windows, the MSVC project will contain both debug and release configuration.
To run the app, you must add QT’s Dll to the development environment.

On Solution Explorer, right click at the “Simple App” project, choose properties.
Then add the path to the QT binaries in Configuration Properties > Debugging > Environment. Please see Figure 6.
Type the QT path such as “path=%path%;C:\QtMSVCx86\QT5.4.1\5.4\msvc2014_opengl\bin” to that field. Before start debugging, right click at “SimpleApp” project and choose “Set as Startup project”. The running project should be able to hit the break point as shown in Figure 7.

For Linux , you can create the code block project by choosing “CodeBlock – Unix Makefiles” (See Figure 2).
Then, add the “Debug” value on “CMAKE_BUILD_TYPE” so that you can use code block to debug project. See Figure 8.
Figure 9 Show the debugging mode of code::block.



Figure 6 Adding the path to the environment


 



Figure 7 Debuging the app on visual studio (Windows)


Figure 8 Set build type to Debug

Figure 9 Debuging the app on code block (Kubuntu Linux)

Packaging

You might notice that in order to run the app, one must add the environment path to QT’s dll (set path=%path%...). Otherwise, you must copy the depended DLL and QML to the app’s directory. You can use dependency walker to find out which .dll you needs. Then, ship your app with those dlls. You can use CMakeList.txt to copy the dlls to the installing package. To do so, install “WIX toolset”. Grasp it from here Wix toolset. The code below is the CMakeList.txt that generate the WIX’s project files and finally create the .msi installer.

IF(WIN32)

    SET(CMAKE_INSTALL_PREFIX "C:/DevBuild/MySimpleApp") 
    SET(CPACK_GENERATOR WIX)

    # Lookup for wix toolset
    IF(${ARCH_32BIT})
    
      SET(CPACK_WIX_ROOT "C:/Program Files/WiX Toolset v3.8"
CACHE PATH "")
      
    # ... code skipped ...
    ENDIF()
    
    SET(CPACK_WIX_UPGRADE_GUID 
"8E9F80D5-452B-4333-B7A4-7FDAAE60CFAC")
    INSTALL(
      TARGETS SimpleApp
      RUNTIME
      DESTINATION programs
      COMPONENT   applications
    )

    # Copy QT's plugins
    # It is not necessary to copy all of these.
    INSTALL(
      DIRECTORY ${QT_INSTALLED_PATH}/plugins/platforms
      DESTINATION programs
      COMPONENT applications
    )
    INSTALL(
      DIRECTORY ${QT_INSTALLED_PATH}/qml/QtQuick
      DESTINATION programs
      COMPONENT applications
    )
    INSTALL(
      DIRECTORY ${QT_INSTALLED_PATH}/qml/QtQuick.2
      DESTINATION programs
      COMPONENT applications
    )

    # Copy QT's dynamic libs
    INSTALL(FILES "${QT_INSTALLED_PATH}/bin/Qt5Core.dll"
                  "${QT_INSTALLED_PATH}/bin/Qt5Widgets.dll"
                  "${QT_INSTALLED_PATH}/bin/Qt5Quick.dll"
                  "${QT_INSTALLED_PATH}/bin/Qt5Qml.dll" 
                  "${QT_INSTALLED_PATH}/bin/Qt5Gui.dll"
                  "${QT_INSTALLED_PATH}/bin/Qt5Network.dll"
                  "${QT_INSTALLED_PATH}/bin/icuuc53.dll"
                  "${QT_INSTALLED_PATH}/bin/icuin53.dll"
                  "${QT_INSTALLED_PATH}/bin/icudt53.dll"
            DESTINATION programs
            COMPONENT applications)
            
    # Do not pack vc++ redist 
    SET(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
    
    INCLUDE(InstallRequiredSystemLibraries)
    
    INSTALL(PROGRAMS    ${CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS}
            DESTINATION programs
            COMPONENT   applications)

    INCLUDE(CPack)
    
ENDIF()
Finally, you can build the installer package by build the “PACKAGE.vcproj”. Figure 10 shows the default instller UI of the built app.


Figure 10 UI of the installer

Disscusion

Creating cross-platfrom C++ project is not that difficult in these days. The setup may take time, but you will do this only once. Most of the code on CMakeList.txt are just if-else statement for choosing the right lib for compiler/OS. Adding new source file or referring to the module in external library are very easy. It is as simple as adding assambly reference in C#.

If you are looking for the way to build Boost by CMake, please read this discussion: http://stackoverflow.com/questions/18354398/what-is-the-best-way-to-build-boost-with-cmake
I am also looking for the best solution for building Boost too.


Update 26-03-2015: I have just found out that boost signal2 is header-only library (but boost signal1 does not). So, I add boost thread to the tutorial project. To ensure that the build system really use static lib.

If you have any trouble on following this tutorial, please inform me.

No comments:

Post a Comment