Building Qt Projects with Linux Cross-toolchains and MSBuild

This tutorial shows how to use the MSBuild subsystem with VisualGDB to build Qt-based projects with Linux cross-toolchains. MSBuild provides better integration with Visual Studio than the QMake build system and allows finer-grain tweaking of various projects options. However, as the Qt projects involve on several Qt-specific tools, using MSBuild for them requires a few manual configuration steps. This tutorial provides a detailed description of those steps and explains the involved tools.

Before you begin, install VisualGDB 5.4 or later.

  1. We will begin with creating a regular Qt project using the QMake build system. Open the VisualGDB Linux Project Wizard and pick the name and the location for your project:01-newprj
  2. On the first page of the wizard select “Create a new project” -> “Application” -> “Qt” and pick your Qt version:02-qt5
  3. On the next page select your cross-toolchain and the target machine:
    03-raspi
  4. Press “Finish”. VisualGDB will install the Qt packages on the target and will copy them inside the cross-toolchain. Once the toolchain is ready, VisualGDB will create a basic Qt project. Build it via Ctrl-Shift-B and ensure it succeeds:04-buildTake a note of the build log as it will contain important command line arguments that we will copy into MSBuild files later. You can find a sample build log using the Raspberry Pi toolchain here.
  5. The project is currently built using the QMake tool that uses its own set of makefiles not directly mapped to Visual Studio targets. Converting the project to MSBuild will let Visual Studio control the build process, improving the build time and letting you create additional targets (e.g. libraries) and tweak per-file properties. Start the conversion by selecting “Convert Project to MSBuild” in the context menu:
    05-convert
  6. VisualGDB will create a new “VisualGDB” platform in the project (and solution) and will copy basic project properties to it. However, this process won’t automatically convert the implicit properties defined by the QMake tool, so attempting to build the project will result in an error:06-error
  7. Instead of storing the Qt-specific properties directly in the project file, we will create a reusable property sheet file that can be referenced from other projects to instantly add Qt support to them. Open View->Other Windows->Property Manager and create a new property sheet:
    07-propsheet
  8. Save the property sheet in a location where it can be referenced by other similar projects:08-name
  9. Locate the “g++” command line for the MainWindow.cpp file in the original build log and also locate 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

    Note that you can replace the toolchain’s sysroot path with just the ‘=’ symbol, e.g. -IC:/SysGCC/raspberry/arm-linux-gnueabihf/sysroot/usr/include/arm-linux-gnueabihf/qt5 can be simplified to just =/usr/include/arm-linux-gnueabihf/qt5.

  10. Enter the extracted command-line options in the corresponding fields of the .props file you created. In our example the props file will look 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/lib/arm-linux-gnueabihf/qt5/mkspecs/arm-linux-gnueabihf;.;=/usr/include/arm-linux-gnueabihf/qt5;=/usr/include/arm-linux-gnueabihf/qt5/QtWidgets;=/usr/include/arm-linux-gnueabihf/qt5/QtGui;=/usr/include/arm-linux-gnueabihf/qt5/QtCore;$(IntDir)</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;GLESv2;pthread</AdditionalLibraryNames>
        </Link>
      </ItemDefinitionGroup>
      <ItemGroup />
    </Project>

    It is also recommended to replace the hardcoded “Debug” path with “$(IntDir)” to allow automatically changing it when switching between debug and release configurations

  11. Try building your project. The build will now fail due to the missing ui_mainWindow.h file:
    09-missingThis happens because we have not yet created the rules for running the Qt UI compiler.
  12. Download the QtProjectItems.xml file to your project directory. It will extend the Visual Studio file type system with 2 new types: Qt Header File and User Interface File.
  13. Edit the .props file to reference QtProjectItems.xml:
      <ItemGroup>
        <PropertyPageSchema Include="$(MSBuildThisFileDirectory)\QtProjectItems.xml"/>
      </ItemGroup>
  14. Reopen the solution and change the item type for MainWindow.h to Qt Header File and MainWindow.ui to User Interface File:10-qhdr
  15. MSBuild builds the projects by creating a list of targets (possibly depending on other targets) and building them in sequence. Each target may run one or more tools. We will now create a target for running the Qt User Interface Compiler (UIC) and Meta-Object Compiler (MOC). Add the following lines to the .props file:
      <PropertyGroup>
        <ClCompileDependsOn>$(ClCompileDependsOn);InvokeQtTools</ClCompileDependsOn>
        <BuildCppObjectListDependsOn>$(BuildCppObjectListDependsOn);InvokeQtTools</BuildCppObjectListDependsOn>
      </PropertyGroup>
      <Target Name="InvokeQtTools" DependsOnTargets="PrepareForBuild;BeginRemoteBuild">
        <!-- Specific tool invocations will go here -->    
      </Target>
  16. Add the rules for invoking the MOC and UIC tools using the arguments captured from the original build log. E.g.:
    <GenericGNUTool 
        Sources="@(QtHeaderFile)" 
        ToolPath="$(ToolchainDir)\Qt\v5"
        ToolchainPrefix=""
        ExtraPath="$(GNUToolchainExtraPath)"
        VisualGDBSettingsFile="$(VisualGDBSettingsFile)"
        TargetPath="$(TargetPath)"
        ToolExeNameBase="moc"
        ResponseFileTag="moc"
        PrimaryOutput="$(IntDir)moc_%(QtHeaderFile.FileName).cpp"
        CommandFormat="-DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I$(ToolchainDir)/sysroot/usr/lib/arm-linux-gnueabihf/qt5/mkspecs/arm-linux-gnueabihf -IC:/tutorials/visualgdb/Linux/QtMSBuild-Cross -I$(ToolchainDir)/sysroot/usr/include/arm-linux-gnueabihf/qt5 -I$(ToolchainDir)/sysroot/usr/include/arm-linux-gnueabihf/qt5/QtWidgets -I$(ToolchainDir)/sysroot/usr/include/arm-linux-gnueabihf/qt5/QtGui -I$(ToolchainDir)/sysroot/usr/include/arm-linux-gnueabihf/qt5/QtCore -I$(ToolchainDir)/include/c++/6 -I$(ToolchainDir)/include/c++/6/backward -Ic:/sysgcc/raspberry/lib/gcc/arm-linux-gnueabihf/6/include -Ic:/sysgcc/raspberry/lib/gcc/arm-linux-gnueabihf/6/include-fixed -I$(ToolchainDir)/include -I$(ToolchainDir)/include/arm-linux-gnueabihf/c++/6 -I$(ToolchainDir)/sysroot/usr/include/arm-linux-gnueabihf -I$(ToolchainDir)/sysroot/usr/include"
        NonRSPArguments="$^ -o $@"
        AdditionalOptions=""
        FastUpToDateCheckDatabaseFile="$(FastUpToDateCheckDatabaseFile)"
        RepeatForEachSource="true"
        RemoteBuildMakefile="$(RemoteBuildMakefile)"
     /> 
     
    <GenericGNUTool Condition="'@(UserInterfaceFile)'!=''"
        Sources="@(UserInterfaceFile)" 
        ToolPath="$(ToolchainDir)\Qt\v5"
        ToolchainPrefix=""
        ExtraPath="$(GNUToolchainExtraPath)"
        VisualGDBSettingsFile="$(VisualGDBSettingsFile)"
        TargetPath="$(TargetPath)"
        ToolExeNameBase="uic"
        ResponseFileTag="uic"
        PrimaryOutput="$(IntDir)ui_%(UserInterfaceFile.FileName).h"
        NonRSPArguments="$^ -o $@"
        CommandFormat=" "
        AdditionalOptions=""
        FastUpToDateCheckDatabaseFile="$(FastUpToDateCheckDatabaseFile)"
        RepeatForEachSource="true"
        RemoteBuildMakefile="$(RemoteBuildMakefile)"
     />
     
    <ItemGroup>
        <ClCompile Include="$(IntDir)moc_%(QtHeaderFile.FileName).cpp"/>
    </ItemGroup>

    This will invoke the MOC and UIC tools for the corresponding source files and will add the files produced by the moc tool to the list of .cpp files to compile.

  17. Now you can build the project as usual:
    11-build
  18. Press F5 to start debugging it and verify that it works:12-hello
  19. If you get a warning about including the FindComponents.props file multiple times, simply remove the explicit reference to it from the .vcxproj file. The MSBuild subsystem will automatically include it when needed, so the explicit reference from the project file is not required:
    13-warning
  20. Now you can reference the .props file you created from any other MSBuild-based project to automatically add Qt libraries to it.

You can find the project shown in this tutorial, including the final versions of all the files in our tutorial repository on GitHub.