Saturday 29 February 2020

Export your Qt Project from VisualStudio to CMake (...or how I stopped worrying and learned to love CMake!)


Yes, I know, this blog was supposed to discuss the high-church themes of languages and system design, and I didn't quite abandoned these goals, but at the momenet I don't have many time to write about deep and interesting things (as you guess, it's time-consuming). To make a long story short, I decided that I'll write about things I'm working on/with just now.

So let us start with CMake and Qt.

1. Intro

You might already have learned that in the next major Qt version (it will be Qt 6) the framework will abandon its old and trusty qmake_build tool and switch to CMake. Why's that?

Although as of lately CMake has acquired a standard tool status in the C++ community, I don't think the desire to be with the cool crowd motivated that change. Rather (AFAIK), qmake's source code developed into an unmaintainable mess, so that nobody wanted to change anything for fear of a total disaster (but feel free to correct me if I'm wrong).

So, as the last alternative to CMake went away, I couldn't advise my client to use qmake for cross platform builds on Windows and Linux with a clear conscience, so in the current project I voted in favour of CMake as our build tool.

You might ask yourself "So what? Just use another tool!". Well, CMake has quite a reputation for being overcomplicated and error-prone. Additionally, it has a text based format, so you will have to become friends with the text editor again.

Before that we could just use convenient graphical IDEs to define the structure of a project, should we lose that comfort now!? If I use Visual Studio with the Qt plugin, I can just drag and drop the Q_OBJECT files into a project and everything just jumps into its place. And what can I do if I already started out with a Visual Studio in first place (as I did in the current project, silly me)!?

2. Convert your Visual Studio Project

So I was loking for some converter of Visual Studion projects and I stumbled upon the the cmake-converter Python tool*. I decised to give it a try and:

1. installed Python 3 first (just googled for the installer and let ir run)

2. Installed the last stable release of cmake-converter:
  pip install cmake-converter
3. Let it run it with:
  cmake-converter -pi -s path/to/file.sln
Where -pi means that we ant use private** includes with CMake and -s specifies a path to the soultion file. You can list all the tool's options using the -h switch. You will see the following output (here I used the verbose output option):


The tool will generate the CMakeLists.txt file as result.

After reading the result file I decided to delete all the generated contents up to the "Source Groups" section and replaced it by my generic Qt-project prologue, i.e.:
  if(UNIX)
   cmake_minimum_required(VERSION 3.10)
  else()
   cmake_minimum_required(VERSION 3.13)
  endif()

  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)

  project(GdmRestApiTest VERSION 0.1.0 LANGUAGES CXX)

  ################################################################################
  # Use solution folders feature
  ################################################################################
  set_property(GLOBAL PROPERTY USE_FOLDERS ON)

  ################################################################################
  # Use Qt
  ################################################################################

  set(CMAKE_AUTOMOC ON)
  set(CMAKE_AUTORCC ON)
  set(CMAKE_AUTOUIC ON)

  #set(CMAKE_AUTOUIC_SEARCH_PATHS "src/ui")

  find_package(Qt5 COMPONENTS Core Widgets Gui Network REQUIRED)
Why did I do that? First, the generated CMake file uses some custom Cmake-Tools file which was a little too complicated at the first sight, and secondly, the generated CMake code doesn't take into account that we are using a Qt plugin!

Thus we added the AUTO-MOC/RC/UI lines and the required Qt packages. However, for that to work we have to tell CMake where the Qt framework files are to be found. It can be done by setting an environment variable CMAKE_PREFIX_PATH to the directory wher the Qt CMake integration files can be found, In my case:

  CMAKE_PREFIX_PATH=C:\Qt\Qt5.12.5\5.12.5\msvc2017_64\lib\cmake .

Next we need to manually add the Form_Files and Resource_Files source groups:
  set(Form_Files
      "./GdmRestApiTest.ui"
  )
  source_group("Form Files" FILES ${Form_Files})

  set(Resource_Files
      "./GdmRestApiTest.qrc"
  )
  source_group("Resource Files" FILES ${Resource_Files})
Now the generated source groups can be reused.

Next we need to include the Form_Files and Resource_Files source groups in ALL_FILES so that AUTO-UI/RC can be applied:
  set(ALL_FILES
      ${Form_Files}
      ${Resource_Files}   
      ${Header_Files__generated}
      ${Source_Files}
      ${Source_Files__generated}
  )
Now, all the source files of the project are specified and we can turn to the definitions needed by compiler and linker. Again I deleted the Target and all the consecutive sections (because, you know, too complicated...) and replaced them by my generic Qt definitions:
  ################################################################################
  # Target
  ################################################################################
  if(MSVC)
   # set /SUBSYSTEM:WINDOWS
   add_executable(${PROJECT_NAME} WIN32 ${ALL_FILES})
  else()
   add_executable(${PROJECT_NAME} ${ALL_FILES})
  endif()

  ################################################################################
  # Include directories
  ################################################################################
  target_include_directories(${PROJECT_NAME} PRIVATE
      "${CMAKE_CURRENT_SOURCE_DIR}/../qt5cpp-openapi/client;"
      "${CMAKE_CURRENT_SOURCE_DIR}/GeneratedFiles;"
  )

  ################################################################################
  # Compiler flags
  ################################################################################
  if(MSVC)
     # quiet warnings related to fopen, sscanf, etc.
     target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
  endif()

  # force more warnings!
  target_compile_options(${PROJECT_NAME} PRIVATE
    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
      Wall>
   $<$<CXX_COMPILER_ID:MSVC>:
     /W4>)

  ################################################################################
  # Dependencies
  ################################################################################
  set(ADDITIONAL_LIBRARY_DEPENDENCIES
    "Qt5::Core;"
    "Qt5::Gui;"
    "Qt5::Network;"
    "Qt5::Widgets"
  )
  target_link_libraries(${PROJECT_NAME} PUBLIC "${ADDITIONAL_LIBRARY_DEPENDENCIES}")
As we see, they define the executable, additional include directories, the compiler flags we need and the library dependencies for the linker. Ready!

3. Run your CMake project

Now it's time to test it out. As Visual Studio has acquired support for directly working with CMake lately, we can use it to test our CMake file right away!

In my Visual Studio 2019 installation (version 16.3.8, Professional) I do the following:
  • Start Visual Studio
  • Choose "Proceed without file" in the starting screen that appears first
  • In Visual Studio's IDE I click "File" > "Open" > "CMake" menu item and choose the CMakeLists.txt file we created.
  • Now we have to wait, as Visual Studio tries to perform the "Configure" and "Generate" steps for the CMakeLists.txt file.
  • After that, we should be able to build the project - select the CMakeLists.txt file in the project panel on the left and choose "Build" in it's context menu (see the pic below).
  • If you want to start the debugger, select the CMakeLists.txt file in the project panel and choose "Set as Current Project". Then just start the debugger clicking on the green arrow button in the toolbar (see the pic below).

Sometimes the automatic CMake generation might fail, or you'd like to reset the project. In that case you select select the CMakeLists.txt file in the project panel and
  • choose "Delete CMake Cache" to reset the project
  • choose "Generate CMake Cache" to recreate the build files
  • start the build as described above
Ready! To thest my Qt application on Linux I have only to:
  • check out the project on the Linux machine
  • open it in Qt Creator
  • and let it build and run!
4. The Moral

I hope you fear CMake no more now! If we aren't overcomplicating things, CMake might indeed be a pretty nice and handy tool for building projects on both Windows and Linux (and of course also on other platforms).

The technique I currently use is:
  • add all .cpp and .hpp files to the Visual Studio project
  • export the project using the cmake-converter tool
  • replace all generated content beside the file lists with my generic CMake prologue and epilogue
  • slightly adapt prologue/epilogue if needed
This works and won't cost you a lot of time.

--
 * "CMake converter for Visual Studio projects" - https://pypi.org/project/cmake-converter/

 ** private CMake includes (i.e. include directories) aren't exported to other CMake projects,