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

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

На этом все. Надеюсь, пригодится кому.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 14
  • 0
    Для выяснения дебажной\отладочной версии юзаю:

    CONFIG(debug, debug|release) {
            	LIBS += 
    	}
    	else{
    		LIBS += 
    	}
    

    • 0
      Здесь это не нужно, имя каталога включает в себя Debug, Release или какое другое имя конфига.
      И проблема вообще-то не в debug vs release, а в том, чтобы Kit нужный узнать. Лучшего пути, чем посмотреть имя каталога для shadow build (вида build-MyProject-Desktop_Qt_5_0_2_MinGW_32bit-Release), я не знаю.
      • НЛО прилетело и опубликовало эту надпись здесь
        • 0
          Что подсказывает? А то мне подсказывается прямо обратное. Все что можно автоматизировать, лучше автоматизировать. Где ручные правки — там ошибки.

          И где эту переменную задавать? В командной строке qmake? Что-то мне подсказывает, что это — плохая идея. Хотя бы потому, что .pro.user файлы нельзя считать частью исходников.
      • 0
        И если вы считаете, что переменной LIBS достаточно для подключения библиотек, то спешу вас разочаровать. LIBS задает только линковку с библиотеками, более ничего.
        • 0
          Я привел пример того, как у я выясняю какую сборку и только.
          Да, и что вы имеете ввиде что вложенные библиотеки не мусорят в INCLUDEPATH? Заносят себя только один раз?
          • 0
            Нет. Я имею в виду, что библиотеки, используемые подключаемыми библиотеками, не добавляют путь к своим заголовочным файлам в INCLUDEPATH проекта. Они только линкуются и добавляются в PRE_TARGETDEPS.
            • 0
              Т.е. они все в backend и не один из классов вложенных не выползают в какой-нибудь шаблон?
              • 0
                Типа того (если я вас правильно понял).

                Какие там библиотеки использует подключаемая библиотека — это вопрос ее реализации. А реализация не должна влиять на интерфейс библиотеки (т.е., заголовочные файлы, которые становятся доступными проекту, к которому подключается библиотека).
            • 0
              Ваш пример совершенно корректен, когда задача — отличить debug от release. Но в данном посте преследуется совершенно другая задача.
              • 0
                Да-да, в первом посте Вы это ясно объяснили. (Я вроде не настаиваю, что надо делать так, как я привел?)
        • 0
          Имхо, qmake совершенно ужасен для хоть сколько-нибудь сложных сценариев сборки. Лучше сбежать на cmake поскорее, чем заниматься такими вот выкрутасами.
          • 0
            Я бы согласился, если бы только Qt Creator с CMake дружил так же хорошо, как с qmake.
            • 0
              Да, с поддержкой cmake у Qt Creator всё печально, к сожалению.

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