красивая настройка работы с подпроектами

Итак по истечении пары десятков лет работы с Qt Creator 2.4.1 и Qt 4.8.1 (qmake подготовитель сборки) наконец-то догадались как можно удобно работать с большими проектами. И это не шутка...

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

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

Вот например наш случай, с которого мы не выдержали и решили сделать проект типа subdirs:

фотка 1

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

Что мы делаем сначала 

Конечно разделяем весь наш функционал по назначению и выделяем их в библиотеки общего назначения. К примеру у нас получилось три:

qp - это наше открытое развитие фреймворка qt , в основном в части sql gyu работы с таблицами.

my_lib - просто разная всячина.

equipment - а это работа с оборудованием (кассовые аппараты, банковские терминалы).

Ну собственно отдельно остается сама наша программа БИТ драйвер ККТ (кстати вы можете ее скачать у нас с сайта).

Как надо оформлять библиотеки

Главное, чтобы у собираемых нами библиотек наружу торчали необходимые функции, классы (и даже переопределение операторов).

Речь вот об этом:

ключевые слова __declspec(dllexport)  и __declspec(dllimport)

Ниже в сырцах Qt есть макрос:

#ifdef _WIN32 || _WIN64
    #define Q_DECL_EXPORT __declspec(dllexport)
    #define Q_DECL_IMPORT __declspec(dllimport)
#else
    #define Q_DECL_EXPORT
    #define Q_DECL_IMPORT
#endif

Тут просто ключевым словам сопоставляются дефайны для среды разработки Qt :

Q_DECL_EXPORT __declspec(dllexport) 
Q_DECL_IMPORT __declspec(dllimport) 

И соответственно работать это будет только для Windows. Далее мы тоже программируем в Windows. Тут все понятно.

И конечно же для экспорта функций из своей библиотеки надо использовать дефайн Q_DECL_EXPORT примерно так.

#ifndef DLL_QP_GLOBAL_H
#define DLL_QP_GLOBAL_H

#if defined(USING_QP_LIB)

#if defined(QP_LIB_IS_EXPORT)
#define QP_LIB_PREFIX Q_DECL_EXPORT // тут идея в том ,
// что здесь мы собираем именно саму библиотеку
#else
#define QP_LIB_PREFIX Q_DECL_IMPORT // тут идея в том, что здесь мы уже используем
// собранную библиотеку в своем проекте, т.е. импортируем как бы
#endif

#else
#  define QP_LIB_PREFIX // этот вариант подходит, когда просто собираем все исходникик в составе своего проекта

#endif

#endif // DLL_QP_GLOBAL_H

Этот файл (qp/dll_qp_global.h)  подключаем ко всем заголовочным файлам своей библиотеки примерно так:

#include "qp/dll_qp_global.h"
class QP_LIB_PREFIX QpCalcColumn

Теперь методы класса QpCalcColumn будут торчать наружу из библиотеки и сможете их использовать в других проектах.

нюанс с QDebug operator<<

QP_LIB_PREFIX QDebug operator<<(QDebug d,const QHash_int_QString &map);

// для оператора << отдельно тоже надо указывать дефайн QP_LIB_PREFIX

Статические отдельные функции как и классы тоже предваряются дефайном QP_LIB_PREFIX

Создаем новый файл проекта pro и подключаем старые данные

Теперь оказывается просто надо создать новый каталог и в нем файл *.pro (у нас app_subdirs), где сделать проект по типу subdirs:

TEMPLATE = subdirs
#TARGET = app

win32:SRC_SUBDIRS += src_app
win32:SRC_SUBDIRS += src_qp
win32:SRC_SUBDIRS += src_eqpt
win32:SRC_SUBDIRS += src_my_lib


OBJECTS_DIR     = tmp\\obj\\$$out_dir
MOC_DIR         = tmp\\moc\\$$out_dir
RCC_DIR         = tmp\\rcc\\$$out_dir
UI_DIR          = ui

#INCLUDEPATH += ../../my_lib/qp

src_app.subdir = $$PWD/../app
src_app.target = sub-main
src_app.depends += src_qp src_my_lib src_eqpt

src_qp.subdir = $$PWD/../../my_lib/qp
src_qp.target = sub-qp

src_my_lib.subdir = $$PWD/../../my_lib
src_my_lib.target = sub_my_lib

src_eqpt.subdir = $$PWD/../../equipment
src_eqpt.target = sub_eqpt src_qp
src_eqpt.depends += src_my_lib

SUBDIRS += $$SRC_SUBDIRS
#    ../some_subdirs_test

Сначала нам показалось, что добавив TARGET = app в корневой проект, мы сможем сразу собирать и запускать app приложение  с отладкой. Но потом выяснили, что для этого есть специально интерактивный способ в Qt Creator выбора запускаемого проекта на закладке запуск.

И вот теперь обратите внимание каким становится наш проект (subdirs), каким правильным и компактным становится дерево проекта. 

фотка 2

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

Теперь мы пожинаем плоды наших усилий

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


Как удобно переключаться обратно на сборку целиком из исходников

Иногда всё-таки надо иметь возможность переключаться обратно на сборку полностью из исходников, то есть без использования библиотек.

Это реализуется конечно же выполнением условия в файле pro, но есть нюанс. Дело в том, что вам придется для сборки из исходников использовать команду include(...) в pro файле вашего проекта.

include нюанс

Так вот include надо отдельно вручную комментировать/раскомментировать, так как при наличии include не важно в какой ветке условия она находится, Qt Creator всегда будет разворачивать весь этот инклуд в дереве вашего проекта (слева) и самое не удобное то, что все что в инклуде все это будет выполняться, ещё раз повторюсь несмотря на то, что , include может находится и внутри не валидного условия.

И бороться с этим никак не возможно, т.к. это функционал самого Qt Creator. Просто это надо всегда учитывать и приспосабливаться.

В качестве рекомендации напрашивается делать инклуд всех библиотек через один инклуд, таким образом меньше придется комментировать/раскомментировать в pro файле.

В общем к этой особенности Qt Creator можно привыкнуть.

Релиз и дебаг

Далее на ничто не помешает разделить релизную и дебажную сборки.

И даже если релиз это статическая сборка тоже не проблема.


unresolved external symbol

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

DEFINES, CONFIG

Надо понимать еще, что в SUBDIRS каждый проект pro собирается отдельно друг от друга. DEFINES одного проекта не влияют на другой. Также CONFIG одного проекта не влияют на другой. А как же нам установить глобальные константы для всех подпроектов. include на писалось выше использовать нельзя. Что же делать?

$$fromflie

Вот это будет то, что надо. Смысл в том, чтобы создать несколько файлов конфигурации например config_release.pri и config_debug.pri (лучше в одном каталоге). Далее все подпроекты должны знать где эти файлы лежат и открывать глобальные переменные из этих фалов через $$fromfile. Таким образом include мы не используем.

Сама Qt использует такой же способ при организации своих исходников.

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



test_check.obj : error LNK2019: unresolved external symbol "public: virtual __thiscall check_payment_Dlg::~check_payment_Dlg(void)" (??1check_payment_Dlg@@UAE@XZ) referenced in function "public: void __thiscall testCheck::on_btn_closeCheck_clicked(void)" (?on_btn_closeCheck_clicked@testCheck@@QAEXXZ)

test_check.obj : error LNK2019: unresolved external symbol "public: __thiscall check_payment_Dlg::check_payment_Dlg(class A_db &,class Check_Content &,class Check_Result &,class aBnk const *,class aKKT const *,class QWidget *)" (??0check_payment_Dlg@@QAE@AAVA_db@@AAVCheck_Content@@AAVCheck_Result@@PBVaBnk@@PBVaKKT@@PAVQWidget@@@Z) referenced in function "public: void __thiscall testCheck::on_btn_closeCheck_clicked(void)" (?on_btn_closeCheck_clicked@testCheck@@QAEXXZ)

test_check.obj : error LNK2019: unresolved external symbol "public: __thiscall BnkTerminals_Setting::BnkTerminals_Setting(class A_db &,class QWidget *)" (??0BnkTerminals_Setting@@QAE@AAVA_db@@PAVQWidget@@@Z) referenced in function "public: bool __thiscall testCheck::init_bnk_from_srv(void)" (?init_bnk_from_srv@testCheck@@QAE_NXZ)

tlv_doc_dlg.obj : error LNK2019: unresolved external symbol "class QDebug __cdecl operator<<(class QDebug,class QMap<int,class QString> const &)" (??6@YA?AVQDebug@@V0@ABV?$QMap@HVQString@@@@@Z) referenced in function "public: void __thiscall Tlv_Doc_Dlg::setDelegate(class Tag_Info const &,int,class QModelIndex const &)" (?setDelegate@Tlv_Doc_Dlg@@QAEXABVTag_Info@@HABVQModelIndex@@@Z)
debug\drv.exe : fatal error LNK1120: 4 unresolved externals

К счастью содержание ошибок однозначно указывает в каком классе их надо искать.

Продолжение


Нюанс с main.cpp файлами

Очень важно не располагать main.cpp (для тестирования вашей библиотеки) в том же каталоге где и файлы библиотеки. То есть лучше делать отдельный каталог типа test и там размещать main.cpp.

Потому, что может случится так, что собирая проект верхнего уровня с вашими библиотеками (с прямым включением исходников библиотек) даже не смотря на то, что main.cpp не включается где-то явно, может получится так, что будет подхватываться другой main.cpp при запуске вашего разрабатываемого приложения.

И перед этим вы еще можете поиметь не понятные проблемы с unresolved external symbol. 

Нюанс с заголовочными файлами с одинаковыми именами

Например полюбили мы использовать везде некий файл app_def.h, где статически прописываются некоторые глобальные константы (например) для всего приложения.

Так вот одинаковые названия файлов это зло (при определенных обстоятельствах).

Как минимум можно перестраховаться использованием конструкции типа #include"app/app_def.h", то есть указанием, где лежит этот файл (например папка app).