Building Qt projects for Linux with MSBuild

This tutorial shows how to setup the VisualGDB MSBuild backend to build Qt-based projects. MSBuild provides much better integration with Visual Studio and supports deeper customization than GNU Make or QMake, but does not support Qt-specific tools like MOC or UIC out-of-the-box and requires creating manual rules to support them.
In this tutorial we will show how to define the necessary manual rules in a reusable file and reference it from subsequent projects. Before you begin, install VisualGDB 5.3 or later.

  1. We will begin with creating a regular Qt project built on a remote machine and then convert it to MSBuild. Open the VisualGDB Linux Project Wizard:01-prjname
  2. Click “Use Qt” and select the “Qt5-based application” template:02-qt5
  3. Select a Linux machine with Qt installed and press “Next” to test the connection and the toolchain:03-machine
  4. Proceed with the default source upload settings and click “Finish”:04-sources
  5. VisualGDB will create a project based on QMake. Build it with Ctrl-Shift-B and take a note of the command lines shown in the “Output” window:05-cmdlineIn this example QMake used the following command lines to build the project:
    1>  /usr/lib/x86_64-linux-gnu/qt5/bin/uic MainWindow.ui -o Debug/ui_MainWindow.h
    1>  g++ -c -m64 -pipe -ggdb -g -Wall -W -D_REENTRANT -fPIC -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -IDebug -IDebug -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64 -o Debug/MainWindow.o MainWindow.cpp
    1>  g++ -c -m64 -pipe -ggdb -g -Wall -W -D_REENTRANT -fPIC -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -IDebug -IDebug -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64 -o Debug/QtProject.o QtProject.cpp
    1>  /usr/lib/x86_64-linux-gnu/qt5/bin/moc -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64 -I/tmp/VisualGDB/c/projects/QtProject -I/usr/include/x86_64-linux-gnu/qt5 -I/usr/include/x86_64-linux-gnu/qt5/QtWidgets -I/usr/include/x86_64-linux-gnu/qt5/QtGui -I/usr/include/x86_64-linux-gnu/qt5/QtCore -I/usr/include/c++/5 -I/usr/include/x86_64-linux-gnu/c++/5 -I/usr/include/c++/5/backward -I/usr/lib/gcc/x86_64-linux-gnu/5/include -I/usr/local/include -I/usr/lib/gcc/x86_64-linux-gnu/5/include-fixed -I/usr/include/x86_64-linux-gnu -I/usr/include MainWindow.h -o Debug/moc_MainWindow.cpp
    1>  g++ -c -m64 -pipe -ggdb -g -Wall -W -D_REENTRANT -fPIC -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/x86_64-linux-gnu/qt5 -isystem /usr/include/x86_64-linux-gnu/qt5/QtWidgets -isystem /usr/include/x86_64-linux-gnu/qt5/QtGui -isystem /usr/include/x86_64-linux-gnu/qt5/QtCore -IDebug -IDebug -I/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64 -o Debug/moc_MainWindow.o Debug/moc_MainWindow.cpp
    1>  g++ -m64 -o Debug/QtProject Debug/MainWindow.o Debug/QtProject.o Debug/moc_MainWindow.o   -L/usr/X11R6/lib64 -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
  6. Right-click on the project in Solution Explorer and select “Convert Project to MSBuild”:06-convert
  7. This will create the “VisualGDB” platform in the solution and the necessary build rules for the normal C++ sources. However building the project will fail due to missing Qt-specific headers:07-buildfail
  8. We will now create an MSBuild property sheet that will define the Qt-specific preprocessor macros and include paths. To do this, open View->Other Windows->Property Manager, right-click on your project and select “Add New Project Property Sheet”:08-newprops
  9. Save the property sheet in a location where it can be reused by other projects:09-propsfile
  10. Look through the QMake build log again and locate the “g++” command line for the MainWindow.cpp file and the final link command line. Then make a list of the following parameters from the command line:
    Parameter Syntax Relevant MSBuild field
    Include search path -I<directory> or -isystem <directory> ClCompile.AdditionalIncludeDirectories
    Preprocessor definitions -D<definition> ClCompile.PreprocessorDefinitions
    Library search directories -L<directory> Link.LibrarySearchDirectories
    Additional library names -l<library> Link.AdditionalLibraryNames
  11. Enter the extracted options in the newly created .props file. In this example the props file looked as follows:
    <?xml version="1.0" encoding="utf-8"?> 
    <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <ImportGroup Label="PropertySheets" />
      <PropertyGroup Label="UserMacros" />
      <PropertyGroup />
      <ItemDefinitionGroup>
        <ClCompile>
          <AdditionalIncludeDirectories>%(ClCompile.PreprocessorDefinitions);.;/usr/include/x86_64-linux-gnu/qt5;/usr/include/x86_64-linux-gnu/qt5/QtWidgets;/usr/include/x86_64-linux-gnu/qt5/QtGui;/usr/include/x86_64-linux-gnu/qt5/QtCore;$(IntDir);/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-g++-64</AdditionalIncludeDirectories>
          <PreprocessorDefinitions>%(ClCompile.PreprocessorDefinitions);_REENTRANT;QT_WIDGETS_LIB;QT_GUI_LIB;QT_CORE_LIB</PreprocessorDefinitions>
          <PositionIndependentCode>true</PositionIndependentCode>
        </ClCompile>
        <Link>
          <LibrarySearchDirectories>%(Link.LibrarySearchDirectories);/usr/X11R6/lib64</LibrarySearchDirectories>
          <AdditionalLibraryNames>%(Link.AdditionalLibraryNames);Qt5Widgets;Qt5Gui;Qt5Core;GL;pthread</AdditionalLibraryNames>
        </Link>
      </ItemDefinitionGroup>
      <ItemGroup />
    </Project>

    Warning: replace the ‘Debug’ item in AdditionalIncludeDirectories with $(IntDir) to allow automatically changing it when switching between debug and release configurations.

  12. Try building your project. The compilation should now succeed, but you should get some ‘undefined reference’ errors:undefNote that you may also hit the “missing ui_MainWindow.h” error instead.
  13. These errors happen because VisualGDB does not know how to run the “moc” and “uic” tools used by Qt. Before we can define a custom target to handle those files, create a file called QtProjectItems.xml in the directory of your .props file and put the following contents in it:
    <?xml version="1.0" encoding="utf-8"?>
    <ProjectSchemaDefinitions
        xmlns="http://schemas.microsoft.com/build/2009/properties">
     
        <ContentType
            Name="UserInterfaceFile"
            DisplayName="UI file"
            ItemType="UserInterfaceFile">
        </ContentType>
     
        <ContentType
            Name="QtHeaderFile"
            DisplayName="Qt Header"
            ItemType="QtHeaderFile">
        </ContentType>
     
        <ItemType Name="UserInterfaceFile" DisplayName="User Interface File"/>
        <ItemType Name="QtHeaderFile" DisplayName="Qt Header File"/>
     
        <FileExtension Name=".ui" ContentType="UserInterfaceFile"/>
    </ProjectSchemaDefinitions>

    This will define two new file types: QtHeaderFile for headers compiled with moc and UserInterfaceFile for .ui files compiled with uic.

  14. Edit the .props file to reference the QtProjectItems.xml:
      <ItemGroup>
        <PropertyPageSchema Include="$(MSBuildThisFileDirectory)\QtProjectItems.xml"/>
      </ItemGroup>
  15. Reopen your project and manually change the “Item type” for MainWindow.h to “Qt Header File”. Similarly set file type for MainWindow.ui to “User Interface File”: qthNote: You can alternatively switch the file types by editing the .vcxproj file directly:
      <ItemGroup>
        <QtHeaderFile Include="MainWindow.h" />
        <UserInterfaceFile Include="MainWindow.ui" />
      </ItemGroup>
  16. Now we can add a custom target that will actually recognize and build the Qt-specific files. Add the following code to the .props file:
      <PropertyGroup>
        <ClCompileDependsOn>$(ClCompileDependsOn);InvokeQtTools</ClCompileDependsOn>
        <BuildCppObjectListDependsOn>$(BuildCppObjectListDependsOn);InvokeQtTools</BuildCppObjectListDependsOn>
      </PropertyGroup>
      <Target Name="InvokeQtTools" DependsOnTargets="PrepareForBuild;BeginRemoteBuild">
     
        
      </Target>
  17. Inside the Target element add the following task:
        <GenericGNUTool 
            Sources="@(QtHeaderFile)" 
            ToolPath=""
            ToolchainPrefix=""
            ExtraPath="$(GNUToolchainExtraPath)"
            VisualGDBSettingsFile="$(VisualGDBSettingsFile)"
            TargetPath="$(TargetPath)"
            ToolExeNameBase="<full path to moc from the log file>"
            ResponseFileTag="moc"
            PrimaryOutput="$(IntDir)moc_%(QtHeaderFile.FileName).cpp"
            CommandFormat="<arguments to moc from the log file except the input and output files>"
            NonRSPArguments="$^ -o $@"
            AdditionalOptions=""
            FastUpToDateCheckDatabaseFile="$(FastUpToDateCheckDatabaseFile)"
            RepeatForEachSource="true"
            RemoteBuildMakefile="$(RemoteBuildMakefile)"
        />       

    The GenericGNUTool statement will tell MSBuild/VisualGDB to run the moc tool for each QtHeaderFile included in the project. It explicitly mentions that the tool should be run once for each header file and that it produces an output called moc_<file name>.cpp. VisualGDB will substitute this name instead of the “$@” token in NonRSPArguments and will also use it to track dependencies between the input and the output file.

  18. Add a similar task for the uic tool:
     <GenericGNUTool Condition="'@(UserInterfaceFile)'!=''"
            Sources="@(UserInterfaceFile)" 
            ToolPath=""
            ToolchainPrefix=""
            ExtraPath="$(GNUToolchainExtraPath)"
            VisualGDBSettingsFile="$(VisualGDBSettingsFile)"
            TargetPath="$(TargetPath)"
            ToolExeNameBase="<full path to uic from the log file>"
            ResponseFileTag="uic"
            PrimaryOutput="$(IntDir)ui_%(UserInterfaceFile.FileName).h"
            NonRSPArguments="$^ -o $@"
            CommandFormat=" "
            AdditionalOptions=""
            FastUpToDateCheckDatabaseFile="$(FastUpToDateCheckDatabaseFile)"
            RepeatForEachSource="true"
            RemoteBuildMakefile="$(RemoteBuildMakefile)"
        />

    Note that the UIC tool does not support response files (files containing saved fragments of command line), so instead we specify the arguments via NonRSPArguments and set CommandFormat to a single space (empty CommandFormat will result in an error).

  19. Finally add the following code inside the Target element:
        <ItemGroup>
            <ClCompile Include="$(IntDir)moc_%(QtHeaderFile.FileName).cpp"/>
        </ItemGroup>

    The ItemGroup/ClCompile definition tells MSBuild to look through all QtHeaderFile items in the project and create virtual ClCompile items (C++ files) using the “$(IntDir)moc_<name of the QtHeaderFile>.cpp” template as if this file was manually added to Solution Explorer.

  20. Once you are done editing the .props file, try building the project again. The build should now succeed although IntelliSense may complain about missing files:11-buildokYou can also download the final version of .props file from this example here. Note that the exact include directories and preprocessor macros can be different if you are using a different Qt version or a different target.
  21. Press F5 to begin debugging the project. Ensure the Qt window appears and works as usual:12-run
  22. To fix the IntelliSense issues, add a custom post-build step via VisualGDB Project Properties that will automatically download the directory with the .h files generated by uic:13-action
  23. Rebuild the project. IntelliSense should now work as usual:14-dl
  24. We will now show how to reuse the Qt .props file for other projects. Create a new MSBuild-based project (not using Qt):15-newprj
  25. Ensure it builds without any errors:16-scratch
  26. Go to Property Manager and reference the .props file created for the previous project:17-props
  27. Copy the Qt-specific source files to the new project and add them to Solution Explorer. Ensure that the Item Type for the .h file states “Qt Header File”:18-hdrfile
  28. Finally adjust the “files to transfer” field in VisualGDB Project Properties to transfer the *.ui files to the Linux side:19-masks
  29. Now you should be able to build and debug the new project as well. As you are now using MSBuild, you can enjoy faster builds, per-file settings, precompiled headers and many other options available via the regular Visual Studio properties for the project and the individual files:props

If you are using a cross-toolchain to build your projects, follow this tutorial to configure MSBuild to build Qt projects with a cross-toolchain.