Безболезненное подключение статических библиотек к проекту средствами qmake

от автора

qbs, несомненно, грядет, но пока мы сидим на qmake (если не сбежали на CMake давным-давно). И, наверное, всякий, кто подключал статические библиотеки к проекту, согласиться со мной, что удовольствие это значительно ниже среднего. Лично я слишком ленив для такого безобразия, и решил автоматизировать процесс. Под катом — то, что у меня получилось.

Пару замечаний. Во-первых, формат поста не позволит мне объяснить детально, что означают многие вещи из упомянутых ниже. Если кому интересны эти подробности — загляните в мой блог, где я написал пухлую серию постов о qmake. Во-вторых, приведенные ниже скрипты на Qt 4 без некоторых переделок работать не будут. Я полностью перешел на Qt 5. И еще — я программирую под Windows, для поддержки других платформы тоже нужно будет вносить небольшие изменения.

Идея

Пусть все мои статические библиотеки имеют имена. А в проект я буду добавлять простым добавлением этого имени в переменную, пусть MYLIBS. Вот так:

MYLIBS += MyAwesomeLib 

При этом должно выполняться следующее:

  1. Должна прилинковываться библиотека, которая скомпилирована в той же конфигурации, в которой компилируется проект.
  2. Если библиотека использует другие библиотеки, то они должны прилинковаться автоматически — но только прилинковаться, INCLUDEPATH засоряться не должен.
  3. Ребилд любой из статических библиотек должен приводить к перелинковке проекта.

Третий пункт реализуется просто, второй тоже несложен, но первый требует наложить какие-то условия на организацию исходников. Лично я всегда использую shadow builds и оставляю имена каталогов в том виде, в котором их генерирует Qt Creator. Тогда нужный вариант библиотеки я могу найти просто по похожему имени каталога.

Настройка фичи

Для реализации переменной MYLIBS я воспользуюсь механизмом фич (features). Самописную фичу можно кинуть в системный каталог (mkscpes/features), но это дурной тон. Я поступил по-другому: создал файл .qmake.cache в корневом каталоге своих исходников (все мои проекты — подкаталоги этого каталога) следующего содержания:

# полный путь к каталогу, куда я кладу свои самописные фичи QMAKEFEATURES = D:/sources/sys/qmake/features 

В этом каталоге я создал файл mylibs.prf, в котором находится собственно реализация MYLIBS. Для того, чтобы переменная MYLIBS заработала, в файле проекта нужно добавить следующую строку:

CONFIG += mylibs 

mylibs.prf

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

# определяю конфигурацию как имя каталога shadow build без имени проекта __outpath = $$basename(OUT_PWD) MYLIB_CONFIG = $$section(__outpath, "-", 2) unset(__outpath)  # префиксы-суффиксы, добавляемые к имени библиотеки win32-msvc* {   MYLIB_PREFIX =   MYLIB_EXT = .lib } else { #mingw   MYLIB_PREFIX = lib   MYLIB_EXT = .a }  # объясняется далее в посте defineReplace(registerStandardMyLib) {   libTargetName = $$1   libFolder = $$2   MYLIB_PATH = $${libFolder}/build-$${libTargetName}-$${MYLIB_CONFIG}/bin/$${MYLIB_PREFIX}$${libTargetName}$${MYLIB_EXT}   isEmpty(MYLIB_NESTED) {     INCLUDEPATH += $${libFolder}/$${libTargetName}/include     export(INCLUDEPATH)   }   isEqual(TEMPLATE, app) {     LIBS += $${MYLIB_PATH}     PRE_TARGETDEPS += $${MYLIB_PATH}     export(LIBS)     export(PRE_TARGETDEPS)   }   return($$MYLIB_PATH) }  # Цикл проходит по всем библиотекам в MYLIBS # для каждой из них инклюдится файл .pri в каталоге lib # рядом с mylib.prf. Имя файла = имени библиотеки. # Если библиотека использует другие библиотеки, то в ее # .pri файле они должны быть указаны в переменной MYLIBS. # Цикл работает до тех пор, пока не будут обработаны все библиотеки.  # 100 уровней вложенности - я параноик __iterlist = 1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0  1 2 3 4 5 6 7 8 9 0   AAA   MYLIB_NESTED = __handled_libs = for(__iter, __iterlist) {   isEqual(__iter, AAA) {     error(MYLIBS: level of nesting limit is reached!)   }   __mylibs = $$unique(MYLIBS)   __mylibs -= __handled_libs   isEmpty(__mylibs): break()   clear(MYLIBS)    for(__mylib, __mylibs) {     !exists($${PWD}/lib/$${__mylib}.pri) {       error(Libary $$__mylib is not configured.)     }     include($${PWD}/lib/$${__mylib}.pri)     __handled_libs += __mylib   }   MYLIB_NESTED = 1  }  unset(__mylib) unset(__iter) unset(__iterlist) unset(__handled_libs) 

Собственно подключение библиотек к проекту происходит в одноименных .pri файлах, которые должны находиться в каталоге lib рядом с фичей mylibs.prf. Если такого файла для подключаемой библиотеки не найдется, то qmake выдаст ошибку.

Файл MyAwesomeLib.pri может выглядеть следующим образом:

MYLIB_PATH = D:/sources/libs/build-MyAwesomeLib-$${MYLIB_CONFIG}/bin/$${MYLIB_PREFIX}$${libTargetName}$${MYLIB_EXT}  # вложенные библиотеки не мусорят в INCLUDEPATH isEmpty(MYLIB_NESTED) {   INCLUDEPATH +=  D:/sources/libs/MyAwesomeLib/include }  # Линковка - только для приложений isEqual(TEMPLATE, app) {   LIBS += $${MYLIB_PATH}   # перелинковывать при изменении библиотеки   PRE_TARGETDEPS += $${MYLIB_PATH} }  # если MyAwesomeLib использует библиотеку MyBeyondAwesomeLib, то нужно это указать MYLIBS = MyBeyondAwesomeLib 

Как видно, писанины много, нужно учитывать разные нюансы вроде обработки вложенности. Учитывая, что я патологически ленив, и почти все мои библиотеки организованы одинаковым образом, я написал функцию registerStandardMyLib, код которой приведен выше в mylibs.prf. Так что абсолютное большинство моих .pri файлов библиотек выглядят следующим образом:

$$registerStandardMyLib(MyAwesomeLib, D:/sources/libs) MYLIBS = MyBeyondAwesomeLib 

На этом все. Надеюсь, пригодится кому.

ссылка на оригинал статьи http://habrahabr.ru/post/180959/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *