[Из песочницы] 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-а, стал выпиливать свой велосипед. Что хотелось получить от решения:

  1. Подключение сторонних библиотек путем прописывания 2-х уровневого include-a, включающего имя библиотеки, например
    #include 
    #include 
    
    

  2. Автоматическая линковка соответствующих *.lib файлов при подключении *.hpp файла
  3. Поддержка x86/x64, Debug/Release, Static/Dynamic Runtime
  4. Возможность быстрого переноса на другую машину


Для начала стоит определиться с папкой, в которой наш репозиторий будет храниться.У меня это C:/nuget++. Внутри созданы папки include, lib/x86, lib/x64, projects. С первыми 3-мя все понятно, а в третьей папке я храню проекты для сборки этих самых библиотек.

Для примера возьмем библиотеку gtest, разархивируем ее в папку projects и откроем gtest-md.sln из папки msvc.

a69ec679061d46d19dce7aa74a2a7aaeДалее, скопируем все заголовочники из 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 готов:

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 и вуаля:

5522b920e6d448a1a74485e16bd9642f

Для переноса полученного репозитория (4 пункт) достаточно скопировать его на другую машину и прописать пути до папок include, lib/x86 и lib/x64.

В качестве бонуса получаем совместимость с boost, достаточно просто скопировать hpp, lib и dll файлы в соответствующий директории. В итоге, потратив 1 рабочий день на организацию совего nuget++ для бедных, я избавился от головной боли при использовании ходовых сторонних библиотек. Сейчас в моем арсенале boost, cryptopp, gmock, gtest, jsoncpp, pugixml, vsqlite и этот список постоянно расширяется. В планах создать единый Solution и настроить Build event-ы для автоматического копирования необходимых файлов в целевые директории.

© Habrahabr.ru