Личный опыт разработки ПО

Сборник рецептов

CMake и Qt

комментариев 29

В заметке представлен способ сборки программ использующих библиотеку Qt. Рассмотрены вопросы локализации приложения и подключения ресурсов, например изображений.

Помимо файлов с примерами есть файл с шаблоном приложения, используя который можно быстро создать достаточно сложный проект.

Для лучшего понимания материала, рекомендую прочитать введение в систему сборки приложений CMake.

Для проекта я рекомендую использовать следующую структуру директорий:

build
	CMakeLists.txt
 
resources
	images
		*.jpg
		*.gif 
		... 
	translations
		*.ts
		*.qm
	... 
	resources.qrc
 
[libraries]
 
[sources]
 
[*.cpp]
[*.h]

Таким образом в проекте существует директория build с файлом для системы сборки, также в этой директории создаются файлы для сборки, файлы генерируемые Qt и здесь же будет находится собранное приложение.

Второй важной директорией является директория resources, в корне которой находится XML файл resources.qrc содержащий список файлов с ресурсами включаемыми в приложение и директориями с ресурсами, например изображениями (images) и файлами с переводом на разные языки (translations).

Также в главной директории могут располагаться директории с подпроектами, файлы и директории с кодом. Под подпроектами я понимаю отдельные директории в корне проекта, содержащие код компилируемый в библиотеки и директорию build с файлом CMakeLists.txt, с помощью которого можно собрать данную библиотеку.

В файле для CMake придется сделать следующие вещи:

  • Найти и подключить библиотеки Qt
  • Из файлов кода сгенерировать MOC-файлы и включить их в компиляцию
  • Указать, что необходимо включать ресурсы в приложение
  • Проверить не были ли изменены файлы с переводом и в случае изменений сделать из них бинарные файлы, которые в свою очередь включить в проект

Поиск и подключение библиотек Qt

Поиск и подключение библиотек в CMake производится при помощи специальных расширений, рассмотрение которых выходит за рамки заметки, пока достаточно знать, что в стандартной поставке модуль для Qt уже есть и его можно использовать:

find_package (Qt4 REQUIRED)

После этого будет предпринята попытка найти Qt и в случае успеха будет создан ряд переменных содержащих полезную информацию. Сейчас интерес для нас представляют QT_USE_FILE и QT_LIBRARIES, которые содержат путь к заголовочным файлам и библиотекам Qt:

include (${QT_USE_FILE})
...
target_link_libraries (${PROJECT} ${QT_LIBRARIES})

Генерация MOC-файлов

Воспользовавшись макросом qt4_wrap_cpp можно из списка исходных кодов получить список MOC-файлов, который в свою очередь установить в зависимость к собираемому проекту:

qt4_wrap_cpp (MOC_SOURCES ${HEADERS})
...
add_executable (${PROJECT} ${HEADERS} ${SOURCES} ${MOC_SOURCES})

В результате выполнения qt4_wrap_cpp переменная MOC_SOURCES будет содержать список сгенерированных MOC-файлов.

Генерация кода из файлов с ресурсами и включение их в приложение

Макрос qt4_add_resources из файла resources.qrc для каждого описанного там файла сгенерирует файлы с кодом, которые можно будет внедрить в приложение. Список этих файлов, как нетрудно догадаться, будет в первой переменной:

qt4_add_resources (QRC_SOURCES ${RESOURCES})
...
add_executable (${PROJECT} ${HEADERS} ${SOURCES} ${MOC_SOURCES} ${QRC_SOURCES})

Файлы с переводом

Механизм локализации приложений Qt предлагает создание текстового файла с переводом (*.ts), работать с которым могут переводчики с помощью программы Linguist и последующей генерации из этих файлов бинарных ресурсов (*.qm) внедряемых в приложение.

Я использую следующий подход:

  • ts-файлы хранятся в репозитории
  • Эти файлы обновляются вручную (цель translations)
  • С ними работают переводчики, поэтому перед комитом кода в репозиторий, необходимо получить последнюю версию файлов, обновить файлы (make translations) и только после этого заливать свой код
  • При изменении ts-файла автоматически должны обновляться qm-файлы
  • qm-файлы должны быть указаны в resources.qrc

Для этих целей я использую следующий подход:

  1. В CMakeLists.txt определяем переменную содержащую список языков поддерживаемых приложением:
    set (LANGUAGES
    	rus
    	eng)
  2. ts и qm файлы будут называться язык.ts и язык.qm, соответственно. Формируем данный список и тут же добавляем команды обновляющие qm-файл при изменении ts-файла:
    foreach (LANGUAGE ${LANGUAGES})
    	set (TS ${TRANSLATIONS_PATH}/${LANGUAGE}.ts)
    	set (QM ${TRANSLATIONS_PATH}/${LANGUAGE}.qm)
    	set (TRANSLATIONS ${TRANSLATIONS} ${TS})
    	set (TRANSLATIONS_BINARY ${TRANSLATIONS_BINARY} ${QM})
    	add_custom_command (
    		OUTPUT ${QM}
    		COMMAND ${QT_LRELEASE_EXECUTABLE} ${TS}
    		MAIN_DEPENDENCY ${TS})
    endforeach()
  3. Добавляем зависимость проекта от файлов перевода:
    add_executable (${PROJECT} ${HEADERS} ${SOURCES} ${MOC_SOURCES} ${QRC_SOURCES} ${TRANSLATIONS})
  4. Добавляем отдельную цель create_translations необходимую для обновления ts-файлов. Отдельная цель сделана для того, чтобы не вызывать утилиту обновления ts-файлов при каждой сборке проекта и защитить ts-файл от неосторожного удаления при запуске make clean.
    add_custom_target (
    	translations
    	COMMAND ${QT_LUPDATE_EXECUTABLE} ${HEADERS} ${SOURCES} -ts ${TRANSLATIONS})
    add_custom_command (
    	TARGET translations
    	COMMAND ${QT_LRELEASE_EXECUTABLE} ${TRANSLATIONS})

Что получилось

cmake_minimum_required (VERSION 2.6)
 
set (PROJECT 
	demo)
 
set (HEADERS 
	../main_window.h)
 
set (SOURCES 
	../main_window.cpp
	../main.cpp)
 
set (LIBRARIES
	library)
 
set (LANGUAGES
	rus
	eng)
 
set (RESOURCE_PATH 
	../resources)
 
set (RESOURCES 
	${RESOURCE_PATH}/resources.qrc)
 
set (TRANSLATIONS_PATH 
	../resources/translations)
 
project (${PROJECT})
 
include_directories (../)
 
find_package (Qt4 REQUIRED)
include (${QT_USE_FILE})
qt4_add_resources (QRC_SOURCES ${RESOURCES})
qt4_wrap_cpp (MOC_SOURCES ${HEADERS})
foreach (LANGUAGE ${LANGUAGES})
	set (TS ${TRANSLATIONS_PATH}/${LANGUAGE}.ts)
	set (QM ${TRANSLATIONS_PATH}/${LANGUAGE}.qm)
	set (TRANSLATIONS ${TRANSLATIONS} ${TS})
	set (TRANSLATIONS_BINARY ${TRANSLATIONS_BINARY} ${QM})
	add_custom_command (
		OUTPUT ${QM}
		COMMAND ${QT_LRELEASE_EXECUTABLE} ${TS}
		MAIN_DEPENDENCY ${TS})
endforeach()
add_custom_target (
	translations 
	COMMAND ${QT_LUPDATE_EXECUTABLE} ${HEADERS} ${SOURCES} -ts ${TRANSLATIONS})
add_custom_command (
	TARGET translations
	COMMAND ${QT_LRELEASE_EXECUTABLE} ${TRANSLATIONS})
 
foreach (LIBRARY ${LIBRARIES})
	add_subdirectory (../${LIBRARY}/build bin/${LIBRARY})
endforeach ()
 
if (MSVC)
	add_definitions (/W3)
elseif (CMAKE_COMPILER_IS_GNUCXX)
	add_definitions (-Wall -pedantic)
else ()
	message ("Unknown compiler")
endif ()
 
source_group ("Header Files" FILES ${HEADERS})
source_group ("Source Files" FILES ${SOURCES})
source_group ("Generated Files" FILES ${MOC_SOURCES})
source_group ("Resource Files" FILES ${QRC_SOURCES})
 
add_executable (${PROJECT} ${HEADERS} ${SOURCES} ${MOC_SOURCES} ${QRC_SOURCES} ${TRANSLATIONS})
 
target_link_libraries (${PROJECT} ${LIBRARIES} ${QT_LIBRARIES})

Последовательность сборки проекта

При применении предложенного подхода процесс сборки проекта выглядит следующим образом:

  1. Получаем код проекта со всеми зависимостями из репозитория
  2. Переходим в директорию build и выполняем команду
    cmake CMakeLists.txt
  3. Создаем qm-файлы командой
    make translations

    или открыв созданный проект с своей среде разработки и собрав подпроект translations

  4. Собираем приложение выполнив
    make

    или выбрав команду build в своей среде разработки

Файл с примером проекта

Шаблон проекта с использование библиотеки Qt и системы сборки CMake

Для удобства я написал шаблон для приложений использующих CMake. Он содержит несколько директорий, файл CMakeLists.txt, пустой ts-файл (default.ts) и resources.qrc. Для использования вам надо будет добавить в CMakeLists.txt путь к файлам с кодом, указать используемые библиотеки и языки. Все переменные вынесены в начало файла и выглядят следующим образом:

set (PROJECT 
	имя_проекта)
 
set (HEADERS 
	заголовочные_файлы)
 
set (SOURCES 
	файлы_с_реализацией)
 
set (LIBRARIES
	директории_с_библиотеками_проекта)
 
set (LANGUAGES
	список_языков)

Также необходимо добавить список файлов с ресурсами в resources.qrc и создать из пустого ts-файла столько файлов, сколько языков в списке, назвав их язык.ts

Файл с шаблоном проекта


Убираем консольное окно в Windows

Если не приложить определенных усилий, приложение под Windows будет запускаться с консольным окном, что удобно при отладке, но не нужно в финальной версии. Чтобы его убрать нужно сделать две вещи:

  1. В команду add_executable добавить WIN32 после ${PROJECT} (на платформах отличных от Windows, это игнорируется, так что можно использовать смело не только под Windows)
  2. В список линкуемых библиотек target_link_libraries добавить ${QT_QTMAIN_LIBRARY}

16th Январь 2010
18:36

Рубрика: Разработка,Сборка

Метки: ,

29 комментариев к 'CMake и Qt'

Подписаться на комментарии по RSS или TrackBack.

  1. Спасибо большое за стать. и за шаблон. Его обязательно попробую заюзать.Как раз стоит задача заюзать cmake вместо qmake. Да и троли вроде бы обещали перейти на cmake

    Vernat

    18 февраля 10 14:53

  2. А как задать пути, в которых необходимо искать файлы? Я не хочу в перечислении файлов указывать у каждого файла путь.QMake это позволяет.Уверен что CMake тоже, не подскажите?!

    Vernat

    18 февраля 10 17:00

  3. Вот так:
    file(GLOB HEADERS ../*.h)
    file(GLOB SOURCES ../*.cpp)
    add_executable(${PROJECT} ${HEADERS} ${SOURCES})
    Но на мой взгляд это в корне неверно, так как сиюминутное удобство может обернуться подключением ненужных файлов в сложном проекте.

  4. В шаблоне и в заметке не хватате про обработку ui форм, созданных qt-designer (qt4_wrap_ui)

    Vernat

    18 февраля 10 17:47

  5. Да я их как-то не использую.. Не прижились они — руками проще.

  6. Когда сам делаешь , то наверно да, руками проще, а когда тебе достается чужой код и там весь ui сделан «типа ручками» не пойми как, то лучше ui от qt-designer-а.
    Я тут поискал, оказалось просто :
    прописываем формы set
    ( FORMS ../ui/mainwindow.ui )
    подключаем текущую директорию в инклюдники, потому что сюда свалятся сгенеренные из *.ui ашники
    include_directories(
    ${CMAKE_CURRENT_BINARY_DIR}
    )
    после генерации moc-файлов вставляем эту для генерации хедеров
    qt4_wrap_ui( UI_HEADERS ${FORMS} )
    и в саму цель добавить ${UI_HEADERS} ,будет выглядеть вот так:
    add_executable (${PROJECT} ${UI_HEADERS} ${HEADERS} ${SOURCES} ${MOC_SOURCES} ${QRC_SOURCES} ${TRANSLATIONS})
    И у меня все заработало на Вашем подправленном шаблоне. Большое спасибо!

    Vernat

    18 февраля 10 21:41

  7. Спасибо за дополнение!

  8. Как говорит автор похожего программерского блога:»Идеи глупо держать под подушкой, там они гниют и тухнут». Я думаю это можно отнести не только к идеям, но и ко всем знаниям.

    Vernat

    18 февраля 10 22:40

  9. А подскажи, плз, как сделать tак, что бы цель собиралась с ключами отладки, к примеру -g (-g2)

    Serge

    2 марта 10 15:01

  10. Чтобы собрать с отладочной информацией надо выполнить:
    cmake CMakeLists.txt -DCMAKE_BUILD_TYPE=debug
    и соответственно без
    cmake CMakeLists.txt -DCMAKE_BUILD_TYPE=release
    Дополнительные ключи компилятору устанавливаются командой add_definitions()

  11. А есть ли способ автоматически построить файл .TS по исходникам, использующим макрос (функцию?) tr()?

    Спасибо!

    Денис

    4 марта 10 1:25

  12. Есть такой способ — можно написать цель которая будет вызывать lupdate список_исходников -ts имя.ts, в результате чего будет создан ts-файл. А можно просто создать пустой ts-файл, а потом обновлять его методом описанным в статье.

  13. >Да я их как-то не использую.. Не прижились они – руками проще.

    Здравствуйте, Максим.
    Если не использовать *.ui, то получается, что файл *.h будет напичкан include’ами для каждого интерфейсного элемента? Или тут тоже какая-то хитрость:)?
    Спасибо, заранее за ответ.

    Иван Русских

    10 марта 10 8:54

  14. Иван, в *.h пишем предварительные объявления, например:
    class QPushButton;
    class QLabel;
    А уже в *.cpp можно одним махом подключить все:
    #include <QtGui>

  15. не подскажите, как указать директорию, куда будет складываться готовый бинарник?

    Vernat

    7 апреля 10 13:45

  16. Для этого существует переменная EXECUTABLE_OUTPUT_PATH для исполняемых файлов и LIBRARY_OUTPUT_PATH для библиотек:
    set (EXECUTABLE_OUTPUT_PATH ../bin)
    Помимо этого для подключаемых подпроектов директория с бинарниками указывается прямо в команде:
    add_subdirectory (../cool_library/build ../bin/cool_library)

  17. Добрый день максим! В qmake есть возможность просто подрубать подпроекты через include(…/*.pri), причем там могут быть тупо перечислены файлы (содержимое переменных подпроекта плюсуется к переменным главного проекта), либо либа, есть ли в cmake подобный элегантный способ, чтобы заюзать эти самые pri файлы либо их содержимое?
    p.s может форум открыть?

    Vernat

    27 мая 10 16:33

  18. Извините пожалуйста за ошибку в написании имени. Неплохо бы редактирование сообщений ввести

    Vernat

    27 мая 10 20:05

  19. а что делать, когда Qt не лежит в дефолтных путях, а, например, собран у себя в хомяке (точнее стоит Qt4SDK).
    сперва он вообще не находил, но после устанавливки: set (QT4_DIR /home/…./qt4/gcc)
    стал выдавать:
    ====
    Warning: QT_QMAKE_EXECUTABLE reported QT_INSTALL_LIBS as /usr/lib
    Warning: But QtCore couldn’t be found. Qt must NOT be installed correctly, or it wasn’t found for cross compiling.
    CMake Error at /usr/share/cmake-2.8/Modules/FindQt4.cmake:639 (MESSAGE):
    Could NOT find QtCore.
    ====

    cap

    29 июля 11 19:34

  20. Достаточно сделать чтобы был доступен qmake, самое простое — в /bin/ сделать символьную ссылку на него. После этого, если Qt был собран правильно, все будет работать.

  21. одна (старая) версия Qt стоит из пакетов в /bin и /usr/lib, но она не нужна для проекта, а нужна та, которая была с установщиком QtSDK. А с последней никак не может справиться.

    cap

    2 августа 11 14:27

  22. Здравствуйте.
    Сразу скажу — впервые «вылезаю из-под windows-одеяла» ))
    Так что продукты вроде autotools, cmake для меня в новинку.

    Интересный момент при сборке для MSVC2010.
    Этап генерации проходит гладко, если не считать сообщений типа
    =========
    Could not copy from: C:/Program Files/CMake 2.8/share/cmake-2.8/Templates/CMakeVSMacros2.vsmacros
    to: C:/Documents and Settings/Admin/?юш фюъєьхэЄ?/Visual Studio 2010/Projects/VSMacros80/CMakeMacros/CMakeVSMacros2.vsmacros

    =========
    Я так понял, что cmake не в состоянии прочитать русское название каталога «Мои документы».
    Тем не менее, Makefile генерится, и без проблем воспринимается IDE MSVC2010.

    Но на этапе сборки в конфигурации Release пропадает картинка! ПРичем в Debug она присутствует.
    Это как так???

    Valerius

    12 мая 12 7:03

  23. В первом случае — не факт. Возможно просто окно вывода студии не может нормально отобразить русский текст, а проблема с правами на запись в директорию Admin. Надо смотреть.

    Во втором случае, еще сложней что-либо посоветовать — мало информации. В каком формате картинка? Если, например gif, то надо смотреть подгружаются ли плагины.

  24. >> В первом случае – не факт. …
    Вряд ли. Речь идет о консольном окне локализованной винды. Русские буквы в нем нормально отображаются. Прав на запись в каталог там особых не требуется. Я через консольку дошел до каталога «Templates/», чередуя русские и английские нащвания в пути и проверил на запись, все в порядке.

    >> Во втором случае, еще сложней что-либо посоветовать …
    Речь идет о Вашем примере, размещенном в данной статье. Картинка там в гифе.
    Компиляция идет без проблем, сборка тоже, причем, собираю и через ИДЕ, и посредством cmake —build -config Release / cmake —build -config Debug.
    В обоих случаях в дебаге на форме отображен «робот с сердечком», а в релизе — нет.

    Valerius

    12 мая 12 14:18

  25. Вот что я сделал для проверки подгрузки плагинов.
    Я вообще использовал вместо gif и QMovie простенький jpg и QPixmap.
    Проект, собранный qmake’ом в IDE QtCreator показал картинку и в дебуге и в релизе.
    Проект, собранный CMake под MSVC-2010 так и не отображает картинку в Release-сборке.
    Не могу пока разобраться, что за ерунда…

    Valerius

    12 мая 12 15:02

  26. Ладно. Забил я на кривость [s]своих рук[/s]/CMakeLists.txt для MSVC-2010.

    Пробую собрать другим генератором, как описано у Вас, а именно
    cmake -G «MinGW Makefiles»
    На этом шаге никаких ошибок.
    Однако, на шаге
    «Создаем qm-файлы командой make translations»
    почему-то подключается MAKE от Borland, и ничего хорошего не происходит.

    Походу, Максим, с учетом предыдущих корявок, описанных мной, никакой универсальности у Вашего рецепта (((

    Valerius

    4 июня 12 14:31

  27. Здравствуйте.
    Никто не подскажет, как в cmake задать такие переменные *.pro файла как VERSION, QMAKE_TARGET_COMPANY и т.д.
    Нужно это для того, чтобы в Windows в свойствах файла отображалась корректная и актуальная информация.
    Если кто-то умеет вместо этого использовать *.rc файл, тоже буду рад пояснениям.
    Спасибо.

    Александр

    6 октября 15 12:57

  28. Mercedes

    MichaelBib

    1 августа 19 5:30

  29. Посоветуйте поставщика строительной техники, по всем предложениям пишите на почту. Заранее благодарны за вашу помощь!

    Mpkrazvitienet

    31 мая 20 20:19

Оставить комментарий