[Из песочницы] Nuget++ для бедных
Если вы регулярно пишете на C++ с использованием сторонних библиотек (окромя boost) и вам надоело постоянно прописывать пути до папок с заголовочниками и lib-ами, то под катом вы найдете один из способов несколько это дело автоматизировать.
Всякий раз, начиная небольшой проект ConsoleApplication в VisualStudio, дабы опробовать пришедшую в голову идею, я испытываю боль. Боль от постоянных настроек в разделах Properties→C/C++→General→Additional Include Directories и Properties→Linker→General→Additional Library Directories.
Если в Solution-е 1–3 проекта и пара сторонних библиотек, то все еще терпимо, но для >10 проектов и > 5 библиотек все выглядит по-другому. И когда однажды мое терпение лопнуло, я решил как-то это дело автоматизировать. Взяв за основу систему для boost-а, стал выпиливать свой велосипед. Что хотелось получить от решения:
- Подключение сторонних библиотек путем прописывания 2-х уровневого include-a, включающего имя библиотеки, например
#include
#include - Автоматическая линковка соответствующих *.lib файлов при подключении *.hpp файла
- Поддержка x86/x64, Debug/Release, Static/Dynamic Runtime
- Возможность быстрого переноса на другую машину
Для начала стоит определиться с папкой, в которой наш репозиторий будет храниться.У меня это C:/nuget++. Внутри созданы папки include, lib/x86, lib/x64, projects. С первыми 3-мя все понятно, а в третьей папке я храню проекты для сборки этих самых библиотек.
Для примера возьмем библиотеку gtest, разархивируем ее в папку projects и откроем gtest-md.sln из папки msvc.
Далее, скопируем все заголовочники из projects/gtest/include/gtest в include/gtest/. Для выполнения первого пункта достаточно добавить ссылку на nuget++/include в глобальные настройки проектов c++. В Visual Studio 2015 (и, насколько я помню, в 2013 тоже) это делает путем открытия окна Property Manager (View→Property Manager), и редактирования файлов Microsoft.Cpp.Win32.user и Microsoft.Cpp.x64.user в любой из конфигураций любого C++ проекта. А именно, нас интересует раздел Common Properties→C/C++→General→Additional Include Directories-просто дописываем тупа путь до include-папки нашего nuget, например C:\nuget++\include;%(AdditionalIncludeDirectories).
Для выполнения 2-го и 3-го пункта придется немного повозиться. Для начала создадим для нашей библиотеки 4 конфигурации для x86/x64 и Static/Dynamic Runtime. В свойствах проекта поменяем имя выходного файла и вариант линковки runtime-библиотеки для каждой из конфигураций соответственно в разделах Properties→Configuration Properties→General→Target Name и Properties→Configuration Properties→C/C++→Code Generation.
Конфигурация | Имя выходного файла | Runtime библиотека |
Release Static Runtime | $(ProjectName)-vc$(PlatformToolsetVersion)-mt-s | Multi-threaded (/MT) |
Release Dynamic Runtime | $(ProjectName)-vc$(PlatformToolsetVersion)-mt | Multi-threaded DLL (/MD) |
Debug Static Runtime | $(ProjectName)-vc$(PlatformToolsetVersion)-mt-sgd | Multi-threaded Debug (/MTd) |
Debug Dynamic Runtime | $(ProjectName)-vc$(PlatformToolsetVersion)-mt-gd | Multi-threaded Debug DLL (/MDd) |
Сделав batch-build, мы получим 2 группы (x86 и x64) по 4 lib-файла, которые необходимо перенести в папки lib/x86 и lib/x64.
Для автоматической линковки указанных файлов придется немного пошаманить над самой библиотекой.Прежде всего добавим в проект файл auto_link.hpp и за-include-им его из всех остальных заголовочников. Покопавшись в MSDN на тему ключей /MT,/MD,/MTd,/MDd можно составить таблицу с уникальным для каждой конфигурации набором макросов. Немного магии с макросами и auto_link.hpp готов:
#pragma once
#if _MSC_VER == 1500
#define GTEST_LIB_TOOLSET "vc90"
#elif _MSC_VER == 1600
#define GTEST_LIB_TOOLSET "vc100"
#elif _MSC_VER == 1700
#define GTEST_LIB_TOOLSET "vc110"
#elif _MSC_VER == 1800
#define GTEST_LIB_TOOLSET "vc120"
#elif _MSC_VER == 1900
#define GTEST_LIB_TOOLSET "vc140"
#endif
#if defined(_MT) || defined(__MT__)
# define GTEST_LIB_THREAD_OPT "mt"
#else
# define GTEST_LIB_THREAD_OPT
#endif
#if defined(_DEBUG)
#if defined(_DLL)
# define GTEST_LIB_RT_OPT "-gd"
#else
# define GTEST_LIB_RT_OPT "-sgd"
#endif
#else
#if defined(_DLL)
# define GTEST_LIB_RT_OPT ""
#else
# define GTEST_LIB_RT_OPT "-s"
#endif
#endif
#ifndef GTEST_LIB
#define GTEST_LIB_FULL_NAME "gtest-" GTEST_LIB_TOOLSET "-" GTEST_LIB_THREAD_OPT GTEST_LIB_RT_OPT ".lib"
#pragma comment(lib, GTEST_LIB_FULL_NAME)
#pragma message("linking to gtest:" GTEST_LIB_FULL_NAME)
#endif
Создаем тестовый проект, несколько строчек кода:
#include
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
TEST(autolink_tests, can_autolink_gtests){}
Build и вуаля:
Для переноса полученного репозитория (4 пункт) достаточно скопировать его на другую машину и прописать пути до папок include, lib/x86 и lib/x64.
В качестве бонуса получаем совместимость с boost, достаточно просто скопировать hpp, lib и dll файлы в соответствующий директории. В итоге, потратив 1 рабочий день на организацию совего nuget++ для бедных, я избавился от головной боли при использовании ходовых сторонних библиотек. Сейчас в моем арсенале boost, cryptopp, gmock, gtest, jsoncpp, pugixml, vsqlite и этот список постоянно расширяется. В планах создать единый Solution и настроить Build event-ы для автоматического копирования необходимых файлов в целевые директории.