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

в 8:58, , рубрики: Qt Software, метки:

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

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

Идея

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

MYLIBS += MyAwesomeLib

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

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

Третий пункт реализуется просто, второй тоже несложен, но первый требует наложить какие-то условия на организацию исходников. Лично я всегда использую 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js