qbs, несомненно, грядет, но пока мы сидим на qmake (если не сбежали на CMake давным-давно). И, наверное, всякий, кто подключал статические библиотеки к проекту, согласится со мной, что удовольствие это значительно ниже среднего. Лично я слишком ленив для такого безобразия, и решил автоматизировать процесс. Под катом — то, что у меня получилось.
Пару замечаний. Во-первых, формат поста не позволит мне объяснить детально, что означают многие вещи из упомянутых ниже. Если кому интересны эти подробности — загляните в мой блог, где я написал пухлую серию постов о qmake. Во-вторых, приведенные ниже скрипты на Qt 4 без некоторых переделок работать не будут. Я полностью перешел на Qt 5. И еще — я программирую под Windows, для поддержки других платформ тоже нужно будет вносить небольшие изменения.
Идея
Пусть все мои статические библиотеки имеют имена. А в проект я буду их добавлять простым добавлением этого имени в переменную, пусть MYLIBS. Вот так:
MYLIBS += MyAwesomeLib
При этом должно выполняться следующее:
- Должна прилинковываться библиотека, которая скомпилирована в той же конфигурации, в которой компилируется проект.
- Если библиотека использует другие библиотеки, то они должны прилинковаться автоматически — но только прилинковаться, INCLUDEPATH засоряться не должен.
- Ребилд любой из статических библиотек должен приводить к перелинковке проекта.
Третий пункт реализуется просто, второй тоже несложен, но первый требует наложить какие-то условия на организацию исходников. Лично я всегда использую shadow builds и оставляю имена каталогов в том виде, в котором их генерирует Qt Creator. Тогда нужный вариант библиотеки я могу найти просто по похожему имени каталога.
Настройка фичи
Для реализации переменной MYLIBS я воспользуюсь механизмом фич (features). Самописную фичу можно кинуть в системный каталог (mkspeсs/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}MyAwesomeLib$${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
На этом все. Надеюсь, пригодится кому.
Автор: mgsxx