Решения в Visual Studio
В отличие от простейших программ, таких как «Hello World», большинство приложений состоит из нескольких исходных файлов. Это обстоятельство порождает массу проблем, в частности, как назвать файлы, где их разместить и можно ли их использовать повторно. В интегрированной среде разработки Visual Studio принята концепция решения (solution), состоящего из ряда проектов, которые в свою очередь состоят из ряда элементов, благодаря которой разработчики могут работать с исходными файлами. Интегрированная среда разработки имеет множество встроенных инструментов, позволяющих упростить этот процесс, обеспечив разработчикам доступ к большей части их приложений. Далее рассматриваются структура решений и проектов, доступные типы проектов и способы настройки их конфигурации.
Структура решения
Работая с системой Visual Studio, пользователь открывает решение. При повторном редактировании специальных файлов создается временное решение, которое можно уничтожить по окончании работы. Однако решение позволяет управлять текущими файлами, поэтому в большинстве случаев его сохранение означает, что пользователь может вернуться к тому, что он делал накануне, и вновь открыть файлы, с которыми он работал.
Решение можно интерпретировать как контейнер связанных между собой проектов. Проекты внутри решения не обязательно должны быть написаны на одном и том же языке программирования или иметь одинаковый тип. Например, решение может содержать веб-приложение ASP.NET, написанное на языке Visual Basic, библиотеку на языке F# и WPF-приложение, написанное на языке C#. Решение позволяет пользователю открыть всё эти проекты в интегрированной среде разработки, а также управлять общими настройками для их создания и развертывания.
Наиболее распространенным способом структурирования приложений в среде Visual Studio является одно отдельное решение, содержащее много проектов. Каждый проект можно создать из набора исходных файлов и папок. Главное окно, в котором пользователь работает с решениями и проектами, называется Solution Explorer:

Для организации работы с исходным кодом и предотвращения его ассоциации с приложениями (за исключением веб-приложений, в которых существуют специальные папки, имеющие особое предназначение в данном контексте) используются папки (folders). Некоторые разработчики используют имена папок, соответствующие пространствам имен, которым принадлежат классы. Например, если класс Person находится в папке DataClasses в проекте FirstProject, то полностью квалифицированное имя класса может выглядеть как FirstProject.DataClasses.Person.
Папки решения (solution folders) — полезный способ организации проектов в большом решении. Они отображаются только в окне Solution Explorer — физически в файловой системе их не существует. Такие действия, как Build или Unload, можно легко выполнять над всеми проектами, включенными в папку решения. Для того чтобы разгрузить окно Solution Explorer, папки решения могут быть свернуты или скрыты.
Скрытые проекты по-прежнему создаются, когда пользователь создает решение. Поскольку папки проекта не соответствуют физическим папкам, их можно добавлять, переименовывать и удалять в любое время, не рискуя повредить связи между файлами или потерять контроль над исходными файлами.
Бытует распространенное заблуждение, что отдельный проект обязательно должен соответствовать отдельной .NET-сборке. Хотя в принципе это верно, довольно часто одну сборку могут представлять несколько проектов. Однако эта ситуация системой Visual Studio 2013 не поддерживается, поэтому мы будем считать, что один проект соответствует одной сборке.
Папка Miscellaneous Files — это специальная папка решения, которую можно использовать для того, чтобы следить за тем, какие еще файлы, не являющиеся частью какого-либо проекта в решении, были открыты в системе Visual Studio. По умолчанию папка Miscellaneous Files скрыта. Для того чтобы сделать ее видимой, следует выполнить команду Tools —> Options —> Environment —> Documents —> Show Miscellaneous Files.
Несмотря на то что формат файла решения, принятый в предыдущих версиях, не изменился, в системе Visual Studio 2010 открыть файл решения, созданный в версии Visual Studio 2013, невозможно.
Кроме информации о файлах, содержащихся в приложении, файлы решения и проектов могут содержать и другие записи, например, о том, как именно должен быть скомпилирован конкретный файл, об установках проекта, о ресурсах и многом другом. Система Visual Studio 2013 имеет немодальное диалоговое окно для редактирования свойств проекта, в то время как свойства решения по-прежнему открываются в отдельном окне. Как и следовало ожидать, свойствами проекта считаются те свойства, которые относятся только к данному проекту, например информация о сборке и связях, а свойства решения определяют общую конфигурацию для сборки приложений.
Формат файла решения
Система Visual Studio 2013 фактически создает для решения два файла, имеющих расширения .suo и .sln (solution file). Первый — это довольно неинтересный бинарный файл, который сложно редактировать. Он содержит информацию, специфичную для пользователя, например, какие файлы были открыты, когда решение закрывалось в последний раз и где находились контрольные точки. Этот файл скрыт, поэтому он не должен появляться в папке решения при использовании Windows Explorer, если не снять с него соответствующую метку.
Иногда файл .suo оказывается поврежденным, и это вызывает непредсказуемые последствия при сборке и редактировании приложений. Если при работе с конкретным решением система Visual Studio становится нестабильной, необходимо выйти из нее и удалить файл с расширением .suo. Он будет создан заново системой Visual Studio, когда решение будет открыто в следующий раз. Файл решения с расширением .sln содержит информацию о решении, например список проектов, конфигурации сборки и другие настройки, не специфичные для проекта. В отличие от многих файлов, используемых в системе Visual Studio 2013, файл решения не является XML-документом. Он хранит информацию в блоках, как показано в следующем примере:
Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 VisualStudioVersion = 12.0.21005.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("") = "GettingStarted", "GettingStarted\GettingStarted.csproj", "" EndProject Project("") = "Information Services", "Information Services\Information Services.csproj", "" EndProject Project("") = "Reference Library", "Reference Library\Reference Library.csproj", "" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution .Debug|Any CPU.ActiveCfg = Debug|Any CPU .Debug|Any CPU.Build.0 = Debug|Any CPU .Release|Any CPU.ActiveCfg = Release|Any CPU .Release|Any CPU.Build.0 = Release|Any CPU .Debug|Any CPU.ActiveCfg = Debug|Any CPU .Debug|Any CPU.Build.0 = Debug|Any CPU .Release|Any CPU.ActiveCfg = Release|Any CPU .Release|Any CPU.Build.0 = Release|Any CPU .Debug|Any CPU.ActiveCfg = Debug|Any CPU .Debug|Any CPU.Build.0 = Debug|Any CPU .Release|Any CPU.ActiveCfg = Release|Any CPU .Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal
В этом примере решение состоит из трех проектов (GettingStarted, Information Services и Reference Library), а раздел Global содержит настройки, которые применяются к решению. Например, само решение будет видимым в окне Solution Explorer, потому что настройка HideSolutionNode установлена равной FALSE. Если изменить ее на TRUE, имя решения не будет отображаться в системе Visual Studio.
Если решение состоит из проектов, которые не нацелены на использование платформы .NET Framework версии 4.0, решение можно открыть в системе Visual Studio 2008, выполнив небольшое редактирование файла .sln. Необходимо просто заменить две первые строки файла приведенными ниже, и решение откроется без ошибок.
Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008
Свойства решения
Для того чтобы открыть диалоговое окно Properties, необходимо щелкнуть правой кнопкой мыши на узле Solution в окне Solution Explorer и выбрать команду Properties. Это диалоговое окно содержит два узла: Common properties и Configuration properties, как показано на рисунке ниже:

Более подробно узлы Common properties и Configuration properties описываются в следующих разделах.
Узел Common Properties
Определяя проект Startup Project для приложения, пользователь имеет три возможности, которые являются практически очевидными. Выбор Current Selection запускает проект, который в данный момент находится в фокусе окна Solution Explorer. Вариант Single Startup гарантирует, что каждый раз будет запускаться один и тот же проект. Эта установка задается по умолчанию, поскольку большинство приложений имеют только один стартовый проект. Последний вариант, Multiple Startup Projects, позволяет запускать несколько проектов в определенном порядке. Это может быть полезным при работе с приложением клиент/сервер в рамках одного решения, причем требуется, чтобы и клиент, и сервер выполнялись одновременно. При выполнении нескольких проектов важно контролировать порядок их запуска. Для управления порядком запуска проектов можно использовать навигационные кнопки, расположенные после списка проектов.
Раздел Project Dependencies используется для того, чтобы задавать другие проекты, от которых зависит конкретный проект. В большинстве случаев система Visual Studio сама управляет этими свойствами, когда пользователь добавляет или удаляет связи между проектами и данным проектом. Однако иногда пользователь может самостоятельно создать связи между проектами, чтобы они собирались в заданном порядке. Система Visual Studio использует этот список зависимостей, для того чтобы определить порядок сборки проектов. Окно этого раздела предотвращает неосторожное добавление циклических связей и удаление необходимых зависимостей между проектами.
В разделе Debug Source Files можно создать список каталогов, в которых система Visual Studio может искать исходные файлы при отладке. Этот список задается по умолчанию и просматривается перед открытием диалогового окна Find Source. Кроме того, пользователь может перечислить исходные файлы, которые система Visual Studio не должна искать. Если щелкнуть на кнопке Cancel в момент, когда система предлагает найти конкретный исходный файл, то он будет добавлен в этот список.
Раздел Code Analysis Settings доступен только в версии Visual Studio Team Suite. Это позволяет выбирать набор правил статического анализа кода, которые будут применяться к конкретному проекту. Более подробно раздел Code Analysis обсуждается далее.
Узел Configuration Properties
И проекты, и решения имеют конфигурации для сборки, определяющие, какие элементы должны быть собраны и почему. Это может сбить пользователя с толку, потому что на самом деле между конфигурацией проекта, определяющей, как должны собираться элементы, и конфигурацией решения, определяющей, какие проекты должны быть собраны, нет никакой корреляции, кроме случаев, когда они имеют одинаковые имена. Новое решение определит конфигурации Debug и Release (решения), что эквивалентно сборке всех проектов в решении с помощью конфигураций Debug и Release (проекта).
Например, может быть создана новая конфигурация решения Test, состоящая из двух проектов: MyClassLibrary и MyClassLibraryTest. Когда пользователь создает свое приложение в конфигурации Test, он хочет, чтобы проект MyClassLibrary был собран в режиме Release, чтобы тестировать его в виде, максимально приближенном к окончательной версии. Однако, чтобы проверить тестируемый код, необходимо собрать тестовый проект в режиме Debug.
Когда пользователь собирает проект в режиме Release, он не хочет, чтобы решение Test было собрано или развернуто вместе с приложением. В данном случае в конфигурации решения Test можно указать, что пользователь хочет, чтобы проект MyClassLibrary был собран в режиме Release, а проект MyClassLibraryTest вообще не собирался.
Пользователь может легко переключаться между этими конфигурациями с помощью меню Configuration стандартной инструментальной панели. Однако, переключаться между платформами не так легко, потому что меню Platform нет ни в одной инструментальной панели. Для того чтобы сделать ее доступной, необходимо выбрать команду View —> Toolbars —> Customize. Затем элемент Solution Platforms из категории Build на закладке Command можно перетащить на инструментальную панель.
Следует отметить, что, выбрав узел Configuration Properties в диалоговом окне Solution Properties, как показано на рисунке ниже, можно получить доступ к раскрывающимся спискам Configuration и Platform. Раскрывающийся список Configuration содержит все доступные конфигурации решения: Debug и Release (заданные по умолчанию), Active и All Configurations. Аналогично в раскрывающемся списке Platform перечислены все доступные платформы. Как только пользователь получит доступ к этим раскрывающимся спискам, он может на этой же странице задать настройки для каждой конфигурации и/или платформы. Для того чтобы добавить новые конфигурации и/или платформы для решения, пользователь может также использовать кнопку Configuration Manager.

При добавлении новых конфигураций решения существует возможность (предусмотренная по умолчанию) создания соответствующих конфигураций для существующих проектов (по умолчанию все проекты будут собираться в новой конфигурации решения), а также возможность создать новую конфигурацию на основе существующих. Если флажок Create Project Configurations установлен и новая конфигурация основана на существующей, то новые конфигурации проекта будут копировать конфигурации проекта, заданные для существующей конфигурации.
Возможности, доступные для создания новых платформенных конфигураций, ограничены типами доступных центральных процессоров: Itanium, x86 и x64. Новая платформенная конфигурация также может основываться на существующих платформенных конфигурациях, и существует возможность создания платформенной конфигурации для проекта.
В конфигурационном файле решения можно также задать тип центрального процессора, для которого оно собирается. Это особенно удобно, если нужно развернуть приложение для компьютеров с 64-битовой архитектурой. Установить настройки для всех этих решений можно непосредственно в контекстном меню, которое открывается после щелчка правой кнопкой мыши на узле Solution node в окне Solution Explorer. В то время как команда Set Startup Projects открывает окно конфигурации решения, команды Configuration Manager, Project Dependencies и Project Build Order открывают окно Configuration Manager and Project Dependencies. Команды Project Dependencies и Project Build Order отображаются в окне, только если решение состоит из нескольких проектов.
Команда Project Build Order открывает окно Project Dependencies и перечисляет порядок сборки, как показано на рисунке ниже:

Закладка Build Order демонстрирует порядок, в котором должны собираться проекты в соответствии с зависимостями между ними. Это может оказаться полезным, если пользователь поддерживает ссылки на бинарные сборки проектов, а не ссылки на проекты. Кроме того, эту возможность можно использовать для двойной проверки того, что проекты будут собраны в правильном порядке.
Чем отличается решение от проекта в Visual Studio и как открыть несколько проектов?
Создал новый проект Weather_bot и хотел открыть уже имеющийся Demo, но он предлагает либо закрыть Weather_bot либо добавить в его решение. От сюда вопросы:
— Чем отличается решение от проекта?
— Можно ли по отдельности открыть проекты просто в разных вкладках (не в одном решении)
— И почему все вкладки называются Program.cs? Нельзя, чтобы было название проекта?
- Вопрос задан более трёх лет назад
- 5378 просмотров
Настраиваем удобную сборку проектов в Visual Studio
Эта статья является руководством по настройке сборки C++ проектов Visual Studio. Частично она сводилась из материалов разрозненных статей на эту тему, частично является результатом реверс-инжениринга стандартных конфигурационных файлов Студии. Я написал ее в основном потому что полезность документации от самой Microsoft на эту тему стремится к нулю и мне хотелось иметь под рукой удобный референс к которому в дальнейшем можно будет обращаться и отсылать других разработчиков. Visual Studio имеет удобные и широкие возможности для настройки по-настоящему удобной работы со сложными проектами и мне досадно видеть что из-за отвратительной документации эти возможности очень редко сейчас используются.
В качестве примера попробуем сделать так чтобы в Студию можно было добавлять flatbuffer schema, а Студия автоматически вызывала flatc в тех случаях когда это нужно (и не вызывала — когда изменений не было) и позволяла задавать настройки напрямую через File Properties

Оглавление
ЗАМЕЧАНИЕ: все приведенные в статье примеры проверялись в VS 2017. В рамках моего понимания они должны работать и в более ранних версиях студии начиная по крайней мере с VS 2012, но обещать я этого не могу.
Level 1: лезем внутрь .vcxproj файлов
Давайте взглянем внутрь типичного .vcxproj автоматически сгенеренного Visual Studio.
Он будет выглядеть как-то примерно так
Debug Win32 Release Win32 Debug x64 Release x64 15.0 Win32Proj protobuftest 10.0.17134.0 Application true v141 Unicode Application false v141 true Unicode Application true v141 Unicode Application false v141 true Unicode true true false false Use Level3 Disabled true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true pch.h Console true Use Level3 Disabled true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true pch.h Console true Use Level3 MaxSpeed true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true pch.h Console true true true Use Level3 MaxSpeed true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true pch.h Console true true true Create Create Create Create
Довольно нечитаемое месиво, не правда ли? И это ведь еще очень небольшой и практически тривиальный файл. Попробуем превратить его во что-то более читабельное и удобное для восприятия.
Поговорим о .props файлах
Для этого обратим пока внимание на то что взятый нами файл — это обычный XML-документ и его можно логически разделить на две части, в первой из которых перечисляются настройки проекта, а во второй — входящие в него файлы. Давайте эти половинки разделим физически. Для этого нам понадобится уже встречающийся в коде тэг Import который является аналогом сишного #include и позволяет включить один файл в другой. Скопируем наш .vcxproj в какой-нибудь другой файл и уберем из него все объявления относящиеся к файлам входящим в проект, а из .vcxproj-а в свою очередь наоборот уберем все кроме объявлений относящихся к файлам собственно входящим в проект. Получившийся у нас файл с настройками проекта но без файлов в Visual Studio принято называть Property Sheets и сохранять с расширением .props. В свою очередь в .vcxproj мы поставим соответствующий Import
Теперь .vcxproj описывает только файлы входящие в проект и читается намного легче
Create Create Create Create
Его можно упростить еще больше, убрав лишние XML-элементы. К примеру свойство «PrecompiledHeader» объявляется сейчас 4 раза для разных вариантов конфигурации (release / debug) и платформы (win32 / x64) но каждый раз это объявление одно и то же. Кроме того у нас здесь используется несколько разных ItemGroup тогда как в реальности вполне достаточно одного элемента. В результате приходим к компактному и понятному .vcxproj который просто перечисляет 1) входящие в проект файлы, 2) то чем является каждый из них (плюс настройки специфичные для конкретных отдельных файлов) и 3) содержит в себе ссылку на хранящиеся отдельно настройки проекта.
Create
Перезагружаем проект в студии, проверяем сборку — все работает.
Но зачем вообще разделять .vcxproj и .props?
Поскольку в сборке ничего не поменялось, то на первый взгляд может показаться что мы поменяли шило на мыло, сделав бессмысленный «рефакторинг» файла в который нам до этого собственно и не было никакой нужды заглядывать. Однако допустим на минутку что в наш solution входит более одного проекта. Тогда, как несложно заметить, несколько разных .vcxproj-файлов от разных проектов могут использовать один и тот же .props файл с настройками. Мы отделили правила сборки используемые в solution от исходного кода и можем теперь менять настройки сборки для всех однотипных проектов в одном месте. В подавляющем большинстве случаев подобная унификация сборки — это хорошая идея. К примеру добавляя в solution новый проект мы в одно действие тривиально перенесем в него подобным образом все настройки из уже существующих в solution проектов.
Но что если нам все же нужны разные настройки для разных проектов? В этом случае мы можем просто создать несколько разных .props-файлов для разных типов проектов. Поскольку .props-файлы могут совершенно аналогичным образом Import-ить другие .props-файлы, то довольно легко и естественно можно выстроить «иерархию» из нескольких .props-файлов, от файлов описывающих общие настройки для всех проектов в solution до узкоспециализированных версий задающих специальные правила для всего одного-двух проектов в solution. В MSBuild действует правило что если одна и та же настройка объявляется во входном файле дважды (скажем вначале импортится в base.props а затем объявляется повторно в derived.props который import-ит в своем начале base.props) то более позднее объявление перекрывает более раннее. Это позволяет легко и удобно задавать произвольные иерархии настроек просто перекрывая в каждом .props файле все необходимые для данного .props-а настройки не заботясь о том что они могли быть где-то уже объявлены ранее. В числе прочего где-нибудь в .props-ах разумно импортировать стандартные настройки окружения Студии которые для C++-проекта будут выгледеть вот так:
Отмечу что на практике весьма удобно класть собственные .props файлы в ту же папку что и .sln файл
Поскольку это позволяет удобно импортировать .props независимо от местоположения .vcxproj
Делаем настройку проекта читабельнее
Теперь когда нам больше не требуется возиться с каждым проектом по отдельности мы можем уделить больше внимания настройке процесса сборки. И для начала я рекомендую дать с помощью .props-файлов вменяемые имена большинству интересных объектов в файловой системе относящихся к solution. Для этого нам следует создать тэг PropertyGroup с пометкой UserMacros:
$(SolutionDir)\.. $(RepositoryRoot)\projects $(RepositoryRoot)\..\ThirdParty $(ThirdPartyDir)\protobuf\src
Тогда в настройках проектов вместо конструкций вида «..\..\..\ThirdParty\protobuf\src\protoc.exe» мы сможем написать просто «$(ProtoBufRoot)\protoc.exe». Помимо большей читабельности это делает код намного мобильнее — мы можем свободно перемещать .vcxproj не боясь что у него слетят настройки и можем перемещать (или обновлять) Protobuf изменив всего одну строчку в одном из .props файлов.
При последовательном объявлении нескольких PropertyGroups их содержимое будет объединено — перезапишутся только макросы имена которых совпадают с ранее объявлявшимися. Это позволяет легко дополнять объявления во вложенных .props файлах не боясь потерять макросы уже объявленные ранее.
Делаем удобным подключение сторонних библиотек
Обычный процесс включения зависимости от thirdparty-библиотеки в Visual Studio частенько выглядит примерно вот так:

Процесс соответствующей настройки включает в себя редактирование сразу нескольких параметров находящихся на разных вкладках настроек проекта и потому довольно зануден. Вдобавок его обычно приходится проделывать по нескольку раз для каждой отдельно взятой конфигурации в проекте, так что нередко в результате подобных манипуляций оказывается что проект в Release-сборке собирается, а в Debug-сборке — нет. Так что это неудобный и ненадежный подход. Но как Вы наверное уже догадываетесь, те же самые настройки можно «упаковать» в props-файл. К примеру для библиотеки ZeroMQ подобный файл может выглядеть примерно так:
$(ThirdPartyDir)\libzmq\include;%(AdditionalIncludeDirectories) ZMQ_STATIC;%(PreprocessorDefinitions) libzmq-v120-mt-sgd-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies) libzmq-v120-mt-s-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies) $(ThirdPartyDir)\libzmq\lib\x64\$(Configuration);%(AdditionalLibraryDirectories)
Обратите внимание что если мы просто определим тэг типа AdditionalLibraryDirectories в props-файле, то он перекроет все более ранние определения. Поэтому здесь используется чуть более сложная конструкция в которой тэг завершается последовательностью символов ;%(AdditionalLibraryDirectories) образующих ссылку тэга самого на себя. В семантике MSBuild этот макрос раскрывается в предыдущее значение тэга, так что подобная конструкция дописывает параметры в начало строки хранящейся в парамере AdditionalLibraryDirectories.
Для подключения ZeroMQ теперь достаточно просто импортировать данный .props файл.
И на этом манипуляции с проектом заканчиваются — MSBuild автоматически подключит необходимые заголовочные файлы и библиотеки и в Release и в Debug сборках. Таким образом потратив немного времени на написание zeromq.props мы получаем возможность надежно и безошибочно подключать ZeroMQ к любому проекту всего в одну строчку. Создатели Студии даже предусмотрели для этого специальный GUI который называется Property Manager, так что любители мышки могут проделать ту же операцию в несколько кликов.

Правда как и остальные инструменты Студии этот GUI вместо читабельного однострочника добавит в код .vcxproj что-то вроде
вот такого кода
Так что я предпочитаю добавлять ссылки на сторонние библиотеки в .vcxproj файлы вручную.
Аналогично тому что уже обсуждалось ранее, работа с ThirdParty-компонентами через .props файлы позволяет так же легко в дальнейшем обновлять используемые библиотеки. Достаточно отредактировать единственный файл zeromq.props — и сборка всего solution синхронно переключится на новую версию. К примеру в наших проектах сборка проекта через этот механизм увязана с менеджером зависимостей Conan который собирает необходимый набор thirdparty-библиотек по манифесту зависимостей и автоматически генерирует соответствующие .props-файлы.
Project Templates — автоматизируем создание проектов
Править вручную .vcxproj-файлы созданные Студией конечно довольно скучно (хотя при наличии навыка и недолго). Поэтому в Студии предусмотрена удобная возможность по созданию собственных шаблонов для новых проектов, которые позволяют провести ручную работу по настройке .vcxproj лишь один раз, после чего повторно использовать ее одним кликом в любом новом проекте. В простейшем случае для этого даже не надо ничего править вручную — достаточно открыть проект который нужно превратить в шаблон и выбрать в меню Project \ Export Template. В открывшемся диалоговом окне можно задать несколько тривиальных параметров вроде имени для шаблона или строки которая будет показываться в его описании, а так же выбрать, будет ли вновь созданный шаблон сразу добавлен в диалоговое окно «New Project». Созданный таким способом шаблон создает копию использованного для его создания проекта (включая все файлы входящие в проект), заменяя в нем только имя проекта и его GUID. В довольно большом проценте случаев этого более чем достаточно.
При более детальном рассмотрении сгенерированного Студией шаблона можно легко убедиться в том что это просто zip архив в котором лежат все файлы использованные в шаблоне и один дополнительный конфигурационный файл с расширением .vstemplate. В этом файле хранится список метаданных проекта (вроде использованной иконки или строки с описанием) и список файлов которые необходимо создать при создании нового проекта. Например,
C++ console application C++ console application for our project VC 1000 true ` OurCppConsoleApp true Enabled true ng.ico console_app.vcxproj.filters main.cpp stdafx.cpp stdafx.h
Обратите внимание на параметр ReplaceParameters=«true». В данном случае он применяется только к vcxproj-файлу который выглядит следующим образом:
$safeprojectname$ Create
На месте GUID и RootNamespace как видите стоят не конкретные значения, а «заглушки» $guid1$ и $safeprojectname$. При использовании шаблона, Студия проходит по файлам помеченным ReplaceParamters=«true», ищет в них заглушки вида $name$ и заменяет их на вычисляемые значения по специальному словарю. По умолчанию Студия поддерживает не очень много параметров, но при написании Visual Studio Extensions (о чем мы поговорим чуть позже) туда нетрудно добавить сколько угодно своих собственные параметров вычисляемых (или вводимых пользователем) при запуске диалога по созданию нового проекта из шаблона. Как можно увидеть в файле .vstemplate, тот же словарь может использоваться и для формирования имени файла что позволяет, в частности, сформировать шаблону уникальные имена .vcxproj-файлов для разных проектов. При задании ReplaceParameters=false файл указанный в шаблоне будет просто скопирован без дополнительной обработки.
Полученный ZIP-архив с шаблоном можно добавить в список шаблонов известных Студии одним из нескольких способов. Проще всего просто скопировать этот файл в папку %USERPROFILE%\Documents\Visual Studio XX\Templates\ProjectTemplates. Стоит заметить, что несмотря на то что в этой папке Вы найдете множество разных подпапок совпадающих по названиям с папками в окне создания нового проекта, по факту шаблон следует положить просто в корневую папку поскольку положение шаблона в дереве новых проектов определяется Студией из тэгов ProjectType и ProjectSubType в .vstemplate-файле. Этот способ удобнее всего подходит для создания «персональных» шаблонов уникальных только для Вас и если Вы выберете в диалоге Export Template галочку «Automatically import template into Visual Studio» то Студия именно это и сделает, поместив созданный при экспорте zip-архив в эту папку с шаблонами. Однако делиться такими шаблонами с коллегами путем их ручного копирования конечно не очень удобно. Поэтому давайте познакомимся с чуть более продвинутым вариантом — создадим Visual Studio Extension (.vsix)

Для создания VSIX нам понадобится установить опциональный компонент Студии который так и называется — средства для разработки Visual Studio Extensions:

После этого в разделе Visual C# \ Extensibility появится вариант «VSIX project». Обратите внимание, что несмотря на свое расположение (C#), он используется для создания любых расширений, в том числе и наборов шаблонов проектов на C++.

В созданном VSIX проекте можно делать массу самых разных вещей — к примеру, создать свое собственное диалоговое окно которое будет использоваться для настройки создаваемых по шаблону проектов. Но это отдельная огромная тема для обсуждения которую я не буду в этой статье затрагивать. Для создания же шаблонов в VSIX все устроено предельно просто: создаем пустой VSIX проект, открываем файл .vsixmanifest и прямо в GUI задаем все данные для проекта. Вписываем метаданные (название расширения, описание, лицензия) на вкладке Metadata. Обратите внимание на расположенное в правом верхнем углу поле «Version» — его желательно указать правильно, поскольку Студия впоследствии использует именно его для определения того какая версия расширения установлена на компьютере. Затем идем на вкладку Assets и выбираем «Add new Asset», с Type: Microsoft.VisualStudio.ProjectTemplate, Source: File on filesystem, Path: (имя к zip-архиву с шаблоном). Нажимаем OK, повторяем процесс пока не добавим в VSIX все желаемые шаблоны.

После этого остается выбрать Configuration: Release и скомандовать Build Solution. Код писать не требуется, править конфигурационные файлы вручную — тоже. На выходе получается переносимый файл с расширением .vsix, который является, по сути, инсталлятором для созданного нами расширения. Созданный файл будет «запускаться» на любом компьютере с установленной Студией, показывать диалог с описанием расширения и лицензией и предлагать установить его содержимое. Разрешив установку — получаем добавление наших шаблонов в диалоговое окно «Создать новый проект»

Подобный подход позволяет легко унифицировать работу большого количества человек над проектом. Для установки и использования шаблонов от пользователя не требуется никакой квалификации кроме пары кликов мышкой. Установленное расширение можно посмотреть (и удалить) в диалоге Tools \ Extensions and Updates
Level 2: настраиваем кастомную компиляцию
ОК, на этом этапе мы разобрались как организованы vcxproj и props файлы и научились их организовывать. Давайте теперь предположим что мы хотим добавить в наш проект парочку .proto схем для сериализации объектов на основе замечательной библиотеки Google Protocol Buffers. Напомню основную идею этой библиотеки: Вы пишите описание объекта («схему») на специальном платформонезависимом мета-языке (.proto-файл) которая компилируется специальным компилятором (protoc.exe) в набор .cpp / .cs / .py / .java / etc. файлов которые реализуют сериализацию / десериализацию объектов по этой схеме в нужном языке программирования и которые Вы можете использовать в своём проекте. Таким образом при компиляции проекта нам нужно первым делом позвать protoc который создаст для нас набор .cpp файлов которые мы в дальнейшем будем использовать.
Традиционный подход
Классическая реализация «в лоб» прямолинейна и состоит в том чтобы просто добавить вызов protoc в pre-build step для проекта которому нужны .proto-файлы. Примерно вот так:

- Требуется явно указывать список обрабатываемых файлов в команде
- При изменении этих файлов билд НЕ будет пересобран автоматически
- При изменении ДРУГИХ файлов в проекте которые Студия распознает как исходные коды, напротив, без нужды будет выполнен pre-build step
- Сгенерированные файлы не входят по умолчанию в сборку проекта
- Если мы включим сгенерированные файлы в проект вручную, то проект будет выдавать ошибку когда мы его будем открывать в первый раз (поскольку файлы еще не сгенерированы первой сборкой).
Знакомимся с MSBuild targets
С точки зрения MSBuild, сборка любого проекта состоит из последовательности сборки сущностей которые называются build targets, сокращенно targets. К примеру сборка проекта может включать в себя выполнение таргета Clean который удалит оставшиеся от предыдущих билдов временные файлы, затем выполнение таргета Compile который скомпилирует проект, затем таргета Link и наконец таргета Deploy. Все эти таргеты вместе с правилами по их сборке не фиксированы заранее а определяются в самом .vcxproj файле. Если Вы знакомы с nix-овой утилитой make и Вам на ум в этот момент приходит слово «makefile», то Вы совершенно правы: .vcxproj является XML-вариацией на тему makefile.
Но стоп-стоп-стоп скажет тут сбитый с толку читатель. Как это так? Мы просмотрели до этого .vcxproj в простом проекте и там не было ни target-ов ни какого-либо сходства с классическим makefile. О каких target-ах тогда может идти речь? Оказывается что они просто «спрятаны» вот в этой строчке включающей в .vcxproj набор стандартных target-ов для сборки C++ — кода.
«Стандартный» билд-план предлагаемый Студией довольно обширен и предлагает большой набор правил для компиляции C++-кода и «стандартных» таргетов типа Build, Clean и Rebuild к которым умеет «цепляться» Студия. Этот набор часто известен под собирательным названием toolset и заменяя в импорте toolset можно заставить Студию компилировать один и тот же проект с помощью другой версии Студии или, к примеру, интеловским компилятором или Clang. Кроме того при желании от стандартного toolset-а можно вообще отказаться и написать свой собственный toolset с нуля. Но мы будем рассматривать в этой статье более простой вариант в котором мы ничего не будем заменять, а лишь дополним стандартные правила необходимыми нам дополнениями.
- Список входов (inputs)
- Список выходов (outputs)
- Зависимости от других targets (dependencies)
- Настройки target-а
- Последовательность фактических шагов выполняемых target-ом (tasks)
то мы добавляем (Include) файл protobuf_tests.cpp в список входов (inputs) данного таргета, а когда пишем
то присваем значение «Use» настройке ClCompile.PrecompiledHeader которую target затем превратит в флаг /Yu переданный компилятору cl.exe.
Попробуем создать target для сборки .proto файлов
Добавление нового target-а реализуется с помощью тэга target:
. steps to take.
Традиционно target-ы выносят в подключаемый файл с расширением .targets. Не то чтобы это было строго необходимо (и vcxproj и targets и props файлы внутри являются равнозначным XML-ем), но это стандартная схема именования и мы будем ее придерживаться. Чтобы в коде .vcxproj файла теперь можно было писать что-то вроде
созданный нами target необходимо добавить в список AvailableItemName
GenerateProtobuf
Нам также понадобится описать что же конкретно мы хотим сделать с нашими входными файлами и что должно получиться на выходе. Для этого в MSBuild используется сущность которая называется «task». Таска — это какое-то простое действие которое нужно сделать в ходе сборки проекта. К примеру «создать директорию», «скомпилировать файл», «запустить команду», «скопировать что-то». В нашем случае мы воспользуемся таской Exec чтобы запустить protoc.exe и таской Message чтобы отобразить этот шаг в логе компиляции. Укажем так же что запуск данного target-а следует провести сразу после стандартного таргета PrepareForBuild. В результате у нас получится примерно вот такой файлик protobuf.targets
GenerateProtobuf
Мы использовали здесь довольно нетривиальный оператор «%» (batching operator) который означает «для каждого элемента из списка» и автоматически добавляемые метаданные. Идея тут в следующем: когда мы записываем код вида
Test
то мы этой записью добавляем в список с названием «ProtobufSchema» дочерний элемент «test.proto» у которого есть дочерний элемент (метадата) AdditionalData содержащая строку «Test». Если мы напишем «ProtobufSchema.AdditionalData» то мы получим доступ к записи «Test». При этом помимо явно объявленных нами метаданных AdditionalData, хитрый MSBuild ради нашего удобства автоматически добавляет к записи еще добрый десяток полезных часто используемых дочерних элементов описанных вот здесь из числа которых мы использовали Identity (исходная строка), Filename (имя файла без расширения) и FullPath (полный путь к файлу). Запись же со знаком % заставляет MSBuild применить описанную нами операцию к каждому элементу из списка — т.е. к каждому .proto файлу по отдельности.
в protobuf.props, переписываем наши proto-файлы в .vcxproj-е на тэг ProtobufSchema
и проверяем сборку
1>—— Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ——
1>Compiling schema test.proto
1>Compiling schema test2.proto
1>pch.cpp
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
Ура! Заработало! Правда наши .proto файлы теперь стали не видны в проекте. Лезем в .vcxproj.filters и вписываем там по аналогии
. Resource Files Resource Files .
Перезагружаем проект — файлы снова видны.
Доводим наш модельный пример до ума
Впрочем на самом деле я немного схитрил. Если не создать вручную перед началом билда папку generated, то билд на самом деле падает
1>. \protobuf_test\protobuf.targets(13,6): error MSB3073: The command «. \ThirdParty\protobuf\bin\protoc.exe —cpp_out=.\generated test.proto» exited with code 1.
Чтобы это исправить добавим вспомогательный target который создаст необходимую папку
С помощью свойства DependsOnTargets мы указываем что перед тем как запускать любую из задач GenerateProtobuf следует запустить PrepareToGenerateProtobuf, а запись @(ProtobufSchema) ссылается на список ProtobufSchema целиком, как единую сущность используемую как вход для этой задачи, так что запущена она будет лишь один раз.
Перезапускам сборку — работает! Давайте попробуем сделать теперь еще раз Rebuild, чтобы уж на этот раз точно во всем убедиться
1>—— Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ——
1>pch.cpp
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
Эм, а куда же пропали наши новые таски? Небольшая отладка — и мы видим что таски на самом деле запускаются MSBuild, но не выполняются поскольку в указанной нами выходной папке уже есть сгенерированные файлы. Проще говоря в Rebuild у нас не работает Clean для .\generated файлов. Исправим это, добавив еще один таргет
Проверяем — работает. Clean очищает созданные нами файлы, Rebuild пересоздает их заново, повторный вызов Build не запускает без нужды пересборку еще раз.
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
Вносим правку в один из C++ файлов, пробуем сделать Build еще раз
1>—— Build started: Project: protobuf_test, Configuration: Debug x64 ——
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
.proto-файл не менялся, поэтому protoc не перезапускался, все ожидаемо. Пробуем теперь изменить .proto файл.
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
Интересно что если запустить сборку MSBuild через командную строку вручную, а не через UI из Студии то такой проблемы не будет — MSBuild корректно пересоберет необходимые .pp.cc файлы. Если мы поменяем какой-нибудь .cpp то запустившийся в студии MSBuild пересоберет не только его, но и .props файл который мы меняли раньше
1>—— Build started: Project: protobuf_test, Configuration: Debug x64 ——
1>Compiling schema test.proto
1>protobuf_test.cpp
1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
U2DCheck и tlog файлы
Оказывается что создатели Visual Studio посчитали что вызывать MSBuild на каждый чих слишком накладно и… реализовали собственную «быструю проверку» на то нужно ли собирать проект или нет. Она называется U2DCheck и если по ее мнению проект не менялся, то Студия просто не станет запускать MSBuild для этого проекта. Обычно U2DCheck работает настолько «тихо» что про ее существование мало кто догадывается но в реестре можно включить полезный флажок который заставит U2DCheck выводить более подробные отчеты.
В своей работе U2DCheck опирается на специальные .tlog файлы. Их легко можно найти в intermediate-output папке (имя_проекта).tlog и чтобы U2DCheck корректно реагировал на изменения в исходных файлах нам надо сделать в этой папке запись в один из read tlog — файлов, а чтобы U2DCheck корректно реагировал на удаление выходных файлов — запись в одном из write tlog — файлов.
Чертыхнувшись, возвращаемся к соответствующей правке нашего target-а
Проверяем — работает: правка .props файла триггерит необходимый ребилд, сборка в отсутствие правки показывает что проект up-to-date. В данном примере для простоты я не стал писать write tlog отслеживающий удаление созданных при компиляции файлов, но он добавляется в target аналогичным образом.
Начиная с Visual Studio 2017 update 15.8 в MSBuild была добавлена новая стандартная таска GetOutOfDateItems которая автоматизирует эту черную магию, но поскольку это произошло совсем недавно то практически все кастомные .target-ы продолжают работать с .tlog файлами вручную.
При желании можно так же полностью отключить U2DCheck для любого проекта добавив одну строчку в поле ProjectCapability
Однако в этом случае Студия будет гонять MSBuild для этого проекта и всех зависящих от него при каждой сборке и да, U2DCheck добавляли не просто так — это работает не так быстро как мне хотелось бы.
Финализуем наш кастомный .target
Получившийся у нас результат вполне работоспособен, но его есть еще куда совершенствовать. К примеру в MSBuild существует режим «выборочной сборки» когда в командной строке указывается что требуется собрать не весь проект в целом, а лишь отдельные конкретно выбранные в нем файлы. Поддержка этого режима требует чтобы таргет проверял содержимое списка @(SelectedFiles).
Помимо этого в нашем исходном коде встречается довольно много повторяющих друг друга строк которые должны совпадать. Правила хорошего тона рекомендуют дать всем этим сущностям читабельные имена и адресовать по этим именам. Для этого часто создается отдельный специальный таргет который создает и заполняет вспомогательный список всеми именами которые понадобятся в дальнейшем.
Наконец мы все еще не реализовали обещанную в самом начале задумку — автоматическое включение сгенерированных файлов в проект. Мы уже можем #include-ить сгенерированные protobuf-ом заголовочные файлы зная что они будут автоматически созданы до того как дело дойдет до компиляции, но с линковщиком этот номер не проходит :). Поэтому просто дописываем сгенерированные файлы в список ClCompile.
Пример подобной причесанной реализации protobuf.targets
GenerateProtobuf .\generated $(ProtobufOutputFolder)\%(ProtobufSchema.Filename).pb.cc $(ProtobufOutputFolder)\%(ProtobufSchema.Filename)_pb2.py %(ProtobufCompilerData.OutputCppFile);%(ProtobufCompilerData.OutputPythonFile) NotUsing
Общие настройки здесь были вынесены в PropertyGroup, а списки входных и выходных файлов заполняет новый target ComputeProtobufInput. Попутно (чтобы продемонстрировать работу со списками выходных файлов) была добавлена генерация кода из схемы для интеграции с python. Запускаем и проверяем что все работает правильно
1>------ Rebuild All started: Project: protobuf_test, Configuration: Debug x64 ------ 1>Compiling schema test.proto 1>Compiling schema test2.proto 1>pch.cpp 1>protobuf_test.cpp 1>test.pb.cc 1>test2.pb.cc 1>Generating Code. 1>protobuf_test.vcxproj -> S:\Temp\msbuild\protobuf_msbuild_integration\x64\Debug\protobuf_test.exe 1>Done building project "protobuf_test.vcxproj". ========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========
А что насчет CustomBuildStep?
Надо сказать что разработчики из Майкрософт вполне здраво оценивали что все вышеописанное, хм, несколько нетривиально и плохо задокументировано и попытались облегчить жизнь программистов введя специальный таргет CustomBuildStep. В рамках этой концепции мы должны были бы в настройках файла отметить что наши .props файлы относятся к типу Custom Build Step

Затем следовало бы указать необходимые шаги по сборке во вкладке Custom Build Step

В .vcxproj-е это выглядит примерно вот так
$(Protoc) --cpp_out=.\generated\%(FileName).pb.cc %(FullPath) Generate protobuf files .\generated\%(FileName).pb.cc . .
Эта конструкция работает за счет того что введенные таким образом данные подставляются в недрах Microsoft.CppCommon.targets в специальный таргет CustomBuildStep который делает, в общем-то, все то же самое что я описал выше. Но работает все через GUI и не надо задумываться о реализации clean и tlog-ах :). При желании этот механизм вполне можно использовать, но я бы не рекомендовал этого делать в силу следующих соображений:
- CustomBuildStep может быть только один на весь проект
- Соответственно так обработать можно лишь 1 тип файлов на весь проект
- Включать такой step в .props файл используемый для подключения ThirdParty библиотеки нецелесообразно, т.к. разные библиотеки могут его перекрывать друг у друга
Правильное копирование файлов
Очень часто встречающейся разновидностью build target является копирование каких-нибудь файлов из одного места в другое. Например копирование файлов ресурсов в папку с собранным проектом или копирование thirdparty DLL к собранному бинарнику. И очень часто эту операцию реализуют «в лоб» через запуск консольной утилиты xcopy в Post-Build Targets. К примеру,

Так делать не надо по тем же самым причинам по которым не надо пытаться запихивать в Post-build steps другие build targets. Вместо этого мы можем напрямую указать Студии что ей необходимо скопировать тот или иной файл. К примеру если файл напрямую входит в проект, то ему достаточно указать ItemType=Copy

После нажатия кнопки apply появится дополнительная вкладка на которой можно настроить куда и как следует копировать выбранный файл. В коде .vcxproj-файла это будет выглядеть примерно так:
. $(OutDir) Всё заработает «из коробки», включая правильную поддержку tlog-файлов. Внутри это реализовано по все тому же принципу «специальной стандартной таски для копирования файлов» что и Custom Build Step которую я критиковал буквально в предыдущем разделе, но поскольку копирование файлов — довольно тривиальная операция и мы не переопределяем саму операцию (копирование) а лишь меняем список входных и выходных файлов для нее то работает это неплохо.
Замечу что при формировании списков файлов CopyFileToFolder можно использовать wildcards. К примеру
$(OutDir) Добавление файлов в список CopyFileToFolders — пожалуй самый простой способ реализовать копирование при сборке проекта, в том числе в .props-файлах подключающих thirdparty-библиотеки. Однако если хочется получить больше контроля над происходящим, то еще одним вариантом является добавление в свои build target специализированной таски Copy. К примеру
Небольшое лирическое отступление
Вообще набор разнообразных стандартных task-ов у MS весьма обширен и включает в себя такие таски как DownloadFile, VerifyFileHash, Unzip и многие другие полезные примитивы. А стандартная таска Copy умеет делать Retry, пропускать не менявшиеся файлы и создавать hard-link вместо тупого копирования если это поддерживается файловой системой.
R сожалению таска Copy не поддерживает wildcards и не заполняет .tlog файлы. При желании это можно реализовать вручную,
к примеру так
$(TargetFolder)\%(MyFilesToCopy.Filename)%(MyFilesToCopy.Extension) Однако работа с стандартным CopyFileToFolders обычно будет намного проще.
Level 3: интегрируемся с GUI от Visual Studio
Все то чем мы до сих пор занимались со стороны может показаться довольно унылой попыткой реализовать в не слишком подходящем для этого инструменте функциональность нормального make. Ручная правка XML-файлов, неочевидные конструкции для решения простых задач, костыльные tlog-файлы… Однако у билд-системы Студии есть и плюсы — к примеру после первоначальной настройки она обеспечивает получившимуся билд-плану неплохой графический интерфейс. Для его реализации используется тэг PropertyPageSchema о котором мы сейчас и поговорим.
Вытаскиваем настройки из недр .vcxproj в Configuration Properties
Давайте попробуем сделать так чтобы мы могли бы редактировать свойство $(ProtobufOutputFolder) из «причесанной реализации protobuf.targets» не вручную в файле, а с комфортом прямо из IDE. Для этого нам потребуется написать специальный XAML-файл с описанием настроек. Открываем текстовый редактор и создаем файл с названием, к примеру, custom_settings.xml
Помимо собственно тэга StringProperty который указывает Студии на существование настройки «ProtobufOutputFolder» с типом String и Subtype=Folder и объясняет то как ее следует показывать в GUI, данный XML-ник указывает что хранить эту информацию следует в project file. Помимо ProjectFile можно использовать еще UserFile — тогда данные будут записаны в отдельный файлик .vcxproj.user который по задумке создателей Студии предназначается для приватных (не сохраняемых в VCS) настроек. Подключаем описанную нами схему к проекту, дописав в наш protobuf.targets тэг PropertyPageSchema
GenerateProtobuf Для того чтобы наши правки вступили в силу перезапускаем Студию, загружаем наш проект, открываем project properties и видим…

Да! Появилась наша страничка с нашей настройкой и ее значение по умолчанию было верно прочитано Студией. Пробуем ее изменить, сохраняем проект, смотрим .vcxproj…
.\generated_custom Как можно видеть по традиционному условию Condition, по умолчанию настройки ассоциированы с конкретной конфигурацией билда. Но при желании это можно перекрыть с помощью установки флага DataSource HasConfigurationCondition=«false». Правда в 2017 студии присутствует баг из-за которого настройки проекта могут не показываться если среди них нет хотя бы одной настройки ассоциированной с какой-то конфигурацией. К счастью эта настройка может быть невидимой.
Вариант без привязки к configuration
Настроек можно добавлять сколько угодно. Возможные типы включают BoolProperty, StringProperty (с опциональными подтипами «folder» и «file»), StringListProperty, IntProperty, EnumProperty и DynamicEnumProperty причем последний может заполняться на лету из любого списка доступного в .vcxproj. Подробнее об этом можно почитать здесь. Можно так же группировать настройки в разделы. Попробуем к примеру добавить еще одну настройку типа Bool

Редактируем настройку, сохраняем проект — все работает как ожидалось
true .\generated_сustom Объясняем Студии про новые типы файлов
До сих пор чтобы добавить в проект protobuf-файл нам необходимо было вручную прописывать в .vcxproj что это . Это легко исправить дописав к упомянутому выше .xml три тэга
Перезапускаем студию, смотрим свойства у наших .proto файлов

Как легко видеть файлы теперь верно распознаются как «Google Protobuf Schema». К сожалению соответствующий пункт не добавляется автоматически в диалог «Add new item», но если мы добавим в проект уже существующий .proto-файл (контекстное меню проекта \ Add \ Existing item… ) то он распознается и добавится правильно. Кроме того наш новый «тип файлов» можно будет выбрать в выпадающем списке Item type:

Ассоциируем настройки с индивидуальными файлами
Помимо настроек «для проекта в целом» совершенно аналогичным образом можно сделать «настройки для отдельного файла». Достаточно указать в тэге DataSource аттрибут ItemType.

Сохраняем, смотрим содержимое .vcxproj
MYLIB_EXPORT Все работает как ожидалось.
Level 4: расширяем функциональность MSBuild
У меня никогда не возникало необходимости залезать в процесс сборки настолько глубоко, но раз уж статья так и так получилась немаленькой, то коротенько упомяну о последней возможности для кастомизации: расширение самого MSBuild. Помимо довольно обширной коллекции «стандартных» тасков, в MSBuild таски можно «импортировать» из разных источников с помощью тэга UsingTask. К примеру мы можем написать свое расширение для MSBuild, скомпилировать его в DLL-библиотеку и импортировать как-то вот так:
Именно так реализовано большинство «стандартных» тасков предоставляемых Студией. Но таскать с собою кастомную DLL для сборки по очевидным причинам частенько неудобно. Поэтому в тэге UsingTask поддерживается штука которая называется TaskFactory. TaskFactory можно считать «компилятором для task-ов» — мы передаем ей на вход некий исходный «мета-код», а она по нему генерирует реализующий его объект типа Task. К примеру с помощью CodeTaskFactory можно воткнуть код написанной на C# таски прямо внутрь .props-файла.
Подобный подход использует, к примеру Qt VS Tools
x) .Select(x => string.Format("[=]", x, Item.GetMetadata(x)))) .ToUpper()); using (var dataZipped = new MemoryStream()) < using (var zip = new DeflateStream(dataZipped, CompressionLevel.Fastest)) zip.Write(data, 0, data.Length); Hash = Convert.ToBase64String(dataZipped.ToArray()); >]]>Если кто-то подобной функциональностью пользовался — отпишитесь об интересных use-case в комментариях.
На этом всё. Надеюсь что мне удалось показать как при настройке MSBuild работу с крупным проектом в Visual Studio можно сделать простой и удобной. Если Вы соберетесь внедрять у себя что-то из описанного выше, то дам небольшой совет: для отладки .props, .targets и .vcxproj удобно выставить MSBuild «отладочный» уровень логгирования в котором он весьма подробно пошагово расписывает свои действия с входными и выходными файлами

Спасибо всем кто дочитал до конца, надеюсь что получилось интересно :).
Делитесь своими рецептами для msbuild в комментариях — я постараюсь обновлять пост чтобы он служил исчерпывающим гайдом по конфигурированию solution в Студии.
0.7 – Компиляция вашей первой программы
Прежде чем мы сможем написать нашу первую программу, нам нужно научиться создавать новые программы в нашей интегрированной среде разработки (IDE). В этом уроке мы расскажем, как это сделать, а вы скомпилируете и запустите свою первую программу!
Проекты
Чтобы написать программу на C++ внутри IDE, мы обычно начинаем с создания нового проекта (мы покажем вам, как это сделать чуть позже). Проект – это контейнер, в котором хранятся все файлы исходного кода, изображения, файлы данных и т.д., которые необходимы для создания исполняемого файла (или библиотеки, веб-сайта и т.д.), который вы сможете запускать или использовать. Проект также сохраняет различные настройки IDE, компилятора и компоновщика, а также запоминает, где вы остановились, чтобы позже, при повторном открытии проекта состояние IDE можно было восстановить с того места, где вы остановились. Когда вы решите скомпилировать свою программу, все файлы .cpp в проекте будут скомпилированы и слинкованы.
Каждый проект соответствует одной программе. Когда вы будете готовы создать вторую программу, вам нужно будет либо создать новый проект, либо перезаписать код в существующем проекте (если вы не хотите его оставлять). Файлы проекта обычно специфичны для конкретной IDE, поэтому проект, созданный в одной IDE, необходимо будет заново создать в другой IDE.
Лучшая практика
Создавайте новый проект для каждой новой программы, которую вы пишете.
Консольные проекты
Когда вы создаете новый проект, вас обычно спрашивают, проект какого типа вы хотите создать. Все проекты, которые мы создадим в этом руководстве, будут консольными. Консольный проект означает, что мы собираемся создавать программы, которые можно запускать из консоли Windows, Linux или Mac.
Ниже показан скриншот консоли Windows:

По умолчанию консольные приложения не имеют графического пользовательского интерфейса (GUI), они выводят текст на консоль, считывают ввод с клавиатуры и компилируются в автономные исполняемые файлы. Они идеально подходят для изучения C++, поскольку сводят сложность к минимуму и обеспечивают работу в самых разных системах.
Не беспокойтесь, если вы никогда раньше не пользовались консолью или не знаете, как получить к ней доступ. Мы будем компилировать и запускать наши программы через наши IDE (которые при необходимости будут вызывать консоль).
Рабочие пространства / решения
Когда вы создаете новый проект для своей программы, многие IDE автоматически добавляют ваш проект в «рабочее пространство» («workspace» или «solution») (термин зависит от IDE). Рабочее пространство – это контейнер, который может содержать один или несколько связанных проектов. Например, если вы пишете игру и хотите иметь отдельные исполняемые файлы для одиночной и многопользовательской игры, вам нужно будет создать два проекта. Обоим этим проектам не имело бы смысла быть полностью независимыми – в конце концов, они являются частью одной игры. Скорее всего, каждый из них будет настроен как отдельный проект в рамках одного рабочего пространства.
Хотя вы можете добавить несколько проектов в одно рабочее пространство, мы обычно рекомендуем создавать новое рабочее пространство для каждой программы, особенно во время обучения. Это проще, и вероятность того, что что-то пойдет не так, меньше.
Написание вашей первой программы
Традиционно у программистов, пишущих на новом языке, первой программой является печально известная программа hello world, и мы не собираемся лишать вас этого опыта! Вы нас потом поблагодарите. Может быть.
Создание проекта в Visual Studio 2019
Когда вы запустите Visual Studio 2019, вы должны увидеть диалоговое окно, которое выглядит следующим образом:

Выберите Создание проекта (Create a new project).
После этого вы увидите диалоговое окно, которое выглядит следующим образом:

Если вы уже открыли предыдущий проект, вы можете открыть это диалоговое окно через меню Файл (File) → Создать (New) → Проект (Project).
Выберите Мастер классических приложений Windows (Windows Desktop Wizard) и нажмите Далее (Next). Если вы этого не видите, то вы, вероятно, при установке Visual Studio забыли выбрать установку Desktop development with C++. В этом случае вернитесь к уроку «0.6 – Установка интегрированной среды разработки (IDE)» и переустановите Visual Studio, как было показано (примечание: вместо полной переустановки вы можете запустить установщик Visual Studio и изменить существующую установку, чтобы добавить поддержку C++).
Далее вы увидите диалоговое окно, которое выглядит следующим образом:

Замените существующее имя проекта на HelloWorld .
Рекомендуется также установить флажок «Поместить решение и проект в одном каталоге» (Place solution and project in the same directory), поскольку это сокращает количество подкаталогов, создаваемых с каждым проектом.
Нажмите Создать (Create), чтобы продолжить.
Наконец, вы увидите последнее диалоговое окно:

Убедитесь, что тип приложения установлен как Консольное приложение (.exe) (Console Application (.exe)), и что параметр Предкомпилированный заголовок (Precompiled Header) не выбран. Затем нажмите ОК.
Вы создали проект! Чтобы продолжить, перейдите в раздел Обозреватель решений Visual Studio ниже.
Создание проекта в Visual Studio 2017 или в более ранней версии
Чтобы создать новый проект в Visual Studio 2017 или более ранней версии, выберите меню Файл (File) → Создать (New) → Проект (Project). Появится диалоговое окно, которое выглядит примерно так:

Сначала убедитесь, что слева указан Visual C++. Если вы не видите Visual C++, возможно, вы забыли выбрать установку поддержку Desktop development with C++ при установке Visual Studio. В этом случае вернитесь к уроку «0.6 – Установка интегрированной среды разработки (IDE)» и переустановите Visual Studio, как было показано (примечание: вместо полной переустановки вы можете запустить установщик Visual Studio и изменить существующую установку, чтобы добавить поддержку C++).
Если вы используете Visual Studio 2017 v15.3 или новее, под Visual C++ выберите Windows Desktop, а затем выберите Windows Desktop Wizard в главном окне.
Если вы не видите вариант с Windows Desktop, возможно, вы используете старую версию Visual Studio. Отлично. Вместо этого выберите Win32, а затем Win32 Console Application в главном окне.
Внизу в поле Name введите название своей программы (замените существующее имя на HelloWorld ). В поле Location вы можете при желании выбрать другое место для размещения вашего проекта. Пока подойдет и значение по умолчанию.
Нажмите ОК. Если вы используете старую версию Visual Studio, запустится мастер приложений Win32. Нажмите Next.
На этом этапе вы должны увидеть диалоговое окно мастера, которое выглядит примерно так (более старые версии Visual Studio используют другой стиль, но имеют большинство из тех же параметров):

Убедитесь, что вы сняли флажок Предкомпилированный заголовок (Precompiled Header).
Затем нажмите ОК или Finish. Теперь ваш проект создан!
Обозреватель решений Visual Studio
В левой или правой части окна вы должны увидеть окно под названием Обозреватель решений. В этом окне Visual Studio создала для вас решение (решение «HelloWorld»). Внутри него, имя которого выделено жирным шрифтом, находится ваш новый проект ( HelloWorld ). Visual Studio создала для вас в этом проекте ряд файлов, в том числе HelloWorld.cpp (в элементе дерева Исходные файлы (Source Files)). Вы также можете увидеть некоторые другие файлы .cpp или .h, которые пока можете игнорировать.

В текстовом редакторе вы увидите, что Visual Studio уже открыла HelloWorld.cpp и создала для вас код. Выделите и удалите весь код и введите/скопируйте следующий код в вашу IDE:
#include int main()
Чтобы скомпилировать программу, либо нажмите F7 (если это не сработает, попробуйте Ctrl + Shift + B ), либо перейдите в меню Сборка (Build) → Собрать решение (Build Solution). Если всё пойдет хорошо, вы должны увидеть следующее в окне вывода:
Сборка начата… 1>------ Сборка начата: проект: HelloWorld, Конфигурация: Debug Win32 ------ 1>HelloWorld.cpp 1>HelloWorld.vcxproj -> C:\Users\user\source\repos\HelloWorld\Debug\HelloWorld.exe ========== Сборка: успешно: 1, с ошибками: 0, без изменений: 0, пропущено: 0 ==========Или, в зависимости от выбранного языка:
1>------ Build started: Project: HelloWorld, Configuration: Debug Win32 ------ 1>HelloWorld.cpp 1>HelloWorld.vcxproj -> c:\users\user\documents\visual studio 2017\Projects\HelloWorld\Debug\HelloWorld.exe ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========Это означает, что ваша компиляция прошла успешно!
Вопрос: У меня вылетела ошибка C1010 («fatal error C1010: unexpected end of file while looking for precompiled header. Did you forget to add ‘#include «stdafx.h»‘ to your source?»). Что теперь?
Вы забыли отключить предварительно скомпилированные заголовки при создании проекта. Заново создайте свой проект (в соответствии с инструкциями выше) и обязательно отключите предварительно скомпилированные заголовки.
Чтобы запустить скомпилированную программу, нажмите Ctrl + F5 или перейдите в меню Отладка (Debug) и выберите Запуск без отладки (Start Without Debugging). Вы увидите следующее:

Это результат выполнения вашей программы! Поздравляем, вы скомпилировали и запустили свою первую программу!
Создание проекта в Code::Blocks
Чтобы создать новый проект, перейдите в меню File (Файл) → New (Новый) → Project (Проект). Появится диалоговое окно, которое выглядит следующим образом:

Выберите Console application (консольное приложение) и нажмите кнопку Go (перейти/создать).
Если вы видите диалоговое окно мастера консольного приложения, нажмите Next (далее), убедитесь, что выбран C++, и снова нажмите Next.
Теперь вам будет предложено назвать ваш проект. Назовите проект HelloWorld . Вы можете сохранить его где угодно. В Windows мы рекомендуем сохранить его в подкаталоге диска C, например C:\CBProjects .

Вы можете увидеть другое диалоговое окно с вопросом, какие конфигурации вы хотите включить. Значения по умолчанию здесь подойдут, поэтому выберите Finish.
Теперь ваш новый проект создан.
В левой части экрана вы должны увидеть окно Management (управление) с выбранной вкладкой Projects (проекты). Внутри этого окна вы увидите папку Workspace с вашим проектом HelloWorld внутри:

Внутри проекта HelloWorld разверните папку Sources (исходники) и дважды щелкните на « main.cpp ». Вы увидите, что для вас уже написана программа hello world!
Замените ее следующим кодом:
#include int main()
Чтобы собрать проект, нажмите Ctrl + F9 или перейдите в меню Build (Сборка) → Build (Сборка). Если всё пойдет хорошо, вы должны увидеть следующее в окне журнала сборки:
-------------- Build: Debug in HelloWorld (compiler: GNU GCC Compiler)--------------- mingw32-g++.exe -Wall -fexceptions -g -std=c++14 -c C:\CBProjects\HelloWorld\main.cpp -o obj\Debug\main.o mingw32-g++.exe -o bin\Debug\HelloWorld.exe obj\Debug\main.o Output file is bin\Debug\HelloWorld.exe with size 1.51 MB Process terminated with status 0 (0 minute(s), 0 second(s)) 0 error(s), 0 warning(s) (0 minute(s), 0 second(s))Это означает, что компиляция прошла успешно!
Чтобы запустить скомпилированную программу, нажмите Ctrl + F10 или перейдите в меню Build (Сборка) → Run (Запуск). Вы увидите что-то похожее на следующий скриншот:

Это результат выполнения вашей программы!
Для пользователей Linux
Пользователям Linux до компиляции в Code::Blocks может потребоваться установить дополнительные пакеты. Дополнительные сведения смотрите в инструкциях по установке Code::Blocks в уроке «0.6 – Интегрированная среда разработки (IDE)».
Если вы используете g++ из командной строки
В этом случае создавать проект не нужно. Просто вставьте следующий код в текстовый файл с именем HelloWorld.cpp и сохраните файл:
#include int main()
В командной строке введите:
g++ -o HelloWorld HelloWorld.cppЭто скомпилирует и слинкует HelloWorld.cpp . Чтобы запустить скомпилированную программу, введите:
HelloWorld./HelloWorldИ вы увидите результат выполнения своей программы.
Если вы используете другие IDE или веб-компилятор
Вам нужно будет самостоятельно выяснить, как сделать следующее:
- создать консольный проект (только для IDE);
- добавить в проект файл .cpp (только для IDE, если он для вас не был создан автоматически);
- вставить в файл следующий код:
#include int main()
Если компиляция завершилась ошибкой
Ничего страшного, сделайте глубокий вдох. Вероятно, мы сможем это исправить. 🙂
Сначала посмотрите на сообщение об ошибке, которое вам выдал компилятор. Чаще всего оно будет содержать номер строки, указывающий, какая строка содержала ошибку. Изучите и эту строку, и строки вокруг нее и убедитесь, что нет опечаток или орфографических ошибок. Также убедитесь, что вы не скопировали в код номера строк.
Во-вторых, посмотрите вопросы и ответы в уроке «0.8 – Несколько распространенных проблем C++», поскольку ваша проблема может быть там освещена.
В-третьих, прочтите комментарии ниже – кто-то мог столкнуться с той же проблемой.
Наконец, если всё вышеперечисленное не помогло, попробуйте поискать сообщение об ошибке в Google. Скорее всего, кто-то уже сталкивался с этим раньше и придумал, как это исправить.
Если ваша программа запускается, но окно мигает и сразу закрывается
Некоторые IDE автоматически не приостанавливают экран консоли после завершения выполнения программы. Ваша программа запускается, но окно вывода закрывается, прежде чем вы сможете просмотреть результаты.
Если это так с вашей IDE, следующие два шага решат вашу проблему:
Сначала добавьте или убедитесь, что следующие строки находятся в верхней части вашей программы (пользователи Visual Studio должны убедиться, что эти строки появляются после #include «pch.h» или #include «stdafx.h» , если таковые существуют):
#include #include
Во-вторых, добавьте следующий код в конец функции main() (непосредственно перед оператором return ):
// сбрасываем все флаги ошибок std::cin.clear(); // игнорируем любые символы во входном буфере, пока не найдем новую строку std::cin.ignore(std::numeric_limits::max(), '\n'); // получаем от пользователя еще один символ std::cin.get();Это приведет к тому, что ваша программа будет ждать, пока пользователь нажмет какую-нибудь клавишу, прежде чем продолжить, что даст вам время изучить вывод вашей программы, прежде чем IDE закроет окно консоли.
Другие решения, такие как обычно предлагаемое system(«pause») , могут работать только в определенных операционных системах, и их следует избегать.
Ваш антивирус также может блокировать выполнение программы. В таком случае попробуйте временно отключить его и посмотреть, решится ли проблема.
Для пользователей Visual Studio
Visual Studio не будет в конце делать паузу в работе консольного приложения, если оно запускается с отладкой (меню Отладка (Debug) → Начать отладку (Start Debugging)). Если вы хотите, чтобы она сделала паузу, вы можете либо использовать приведенное выше решение с дополнительным кодом, либо запустить свою программу без отладки (меню Отладка (Debug) → Начать отладку (Start Without Debugging)).
Заключение
Поздравляем, вы прошли самую сложную часть этого руководства (установку IDE и компиляцию вашей первой программы)!
Не волнуйтесь, если не понимаете, что делают все строки в программе HelloWorld . Мы рассмотрим и подробно объясним каждую строку в начале следующей главы.
