Visual Studio Extensibility. Часть первая: MSBuild

Привет Хабр, в этих статьях я попытаюсь осветить тему расширений Microsoft Visual Studio (а попутно ещё и MSBuild), т.к. эта сфера является крайне плохо документированной и вообще покрыта пеленой какой-то загадочности.9aef5c842cb24d9ca2a27ca180c862a3.png

ПрологЯ являюсь профессиональным разработчиком C++ с достаточно большим опытом работы, а также большим поклонником продукции компании Microsoft и в первую очередь главного моего инструмента разработки — Visual Studio. Не так давно, в качестве хобби, я начал заниматься программированием микроконтроллеров, и микроконтроллеры я выбрал от компании Microchip. Единственное, что меня не устраивало это использование инструментов разработки, которые предоставляет сама компания Microchip. Я ничего не могу сказать плохого про эти продукты, просто я не хотел бы устанавливать несколько IDE на свой рабочий (или домашний) компьютер, поэтому родилась мысль интегрировать компилятор XC8 от компании Microchip в Microsoft Visual Studio. Позже я увидел в этой идее ещё один плюс — множество моих (и не только моих) проектов прямо или косвенно связаны с подключением их к компьютеру, поэтому приходится разрабатывать ответную программную часть — было бы здорово объединять их в одно решение (solution) с проектом микропрограммы. Проковырявшись большое количество времени, я понял, что тема интеграции чего либо в Visual Studio является эдаким белом пятном: нормального описания нет, куцые описания в блогах, и что самое плохое — практически нет примеров. Собрав кое какую информацию по крупицам, кое-что поняв из описания и опираясь на метод научного тыка, я решил поделиться полученными знаниями. Итак, поехали.План действий Ну раз уж мы решили расширить функционал Visual Studio прикручиванием стороннего компилятора, то давайте расширим его максимально возможным образом. Для этого определимся со списком того, что мы хотим сделать: Чтобы при просмотре свойств проекта (Project Properties) в Visual Studio — были бы наши собственные свойства. Определить свой набор расширений файлов, которые можно было бы включать в проект. Естественно определить, свою систему сборки, чтобы вызывались наши компиляторы, линковщики и т.д. Создать свой тип проекта, чтобы студия могла открывать файл с нашим собственным расширением. Создать волшебник для генерации этого типа проекта. Ну что же, попробуем осуществить из этого списка хотя бы часть требований.

Начнем, пожалуй, с теории: Microsoft Visual Studio — чуть менее чем полностью построена на технологии COM. Поэтому нужно быть готовым столкнуться с ней. Хотя мы попробуем все же этого избежать. Любой проект Microsoft Visual Studio является скриптом MSBuild. Ввиду выше перечисленного, для расширения возможностей студии в первую очередь нам придется изучить устройство системы сборок, именуемое MSBuild.

Сразу оговорюсь: у меня под рукой была достаточно старая VIsual Studio 2010 — поэтому все описание будет для нее, но я полагаю, что с 2012 и 2013 студиями все будет аналогично.

Итак открываем студию, создаем Blank Solution и добавляем в него Empty Project (мне ближе проект в категории С++, поэтому я выбрал его)d7330d7dc9004913bcfd8adb03b35364.png

Я назвал и проект и решение «test» и сделал так, чтобы они лежали в одной директории (это удобно для наших экспериментов, объясню позже)- таким образом получив файлы test.sln и test.vcxproj

Теперь закрываем студию и берем какой нибудь текстовый редактор (лучше с подсветкой XML синтаксиса — в принципе подойдет та же студия, только уже другой её экземпляр) и открываем test.vcxproj как текстовый файл.

Посмотрим, что внутри у test.vcxproj:

test.vcxproj Debug Win32 Release Win32 {E1064D79-B415–4EDC-9FAC-C50E4102268B} test Application true MultiByte Application false true MultiByte Level3 Disabled true Level3 MaxSpeed true true true true true Самое главное, что необходимо увидеть здесь: тег ProjectConfiguration. тег ProjectGuid. файлы, которые подключаются тегом Import. Все остальное можно спокойно удалять. Также предлагаю удалить файл test.filters, чтобы на не мешались виртуальные директории в проекте. Debug Win32 Release Win32 {E1064D79-B415–4EDC-9FAC-C50E4102268B} test Приведенный выше файл абсолютно валидный с точки зрения студии и MSBuild, его можно открыть и даже собрать.Также предлагаю сразу разобраться со сборкой нашего проекта из командной строки:

Запускаем Microsoft Visual Studio Command Prompt Переходим в директорию, где находится test.vcxproj Выполняем msbuild test.vcxproj /p: Configuration=Debug /p: Platform=Win32 Можно написать скрипт для автоматизации данного процесса, чтобы было удобнее проверять сборку на предмет ошибок и прочего.Теперь начинаем разбираться в том, что отвечает за свойства проекта и вообще, что делает наш проект — проектом студии. А делают это все строчки:

Импортируемые файлы у меня находятся с директории C:\Program Files\MSBuild\Microsoft.Cpp\v4.0Можно конечно попробовать их посмотреть, но я боюсь человек, который никогда не разбирался с MSBuild, вряд ли что-то поймет, а желание разбираться в этом во всем мгновенно улетучится. Так что я предлагаю пока туда не смотреть, т.к. можно потратить уйму времени (как сделал автор) и все равно ничего не понять.

Важно!: При каждой правке vcxproj и сопутствующих файлов необходимо перезапускать студию полностью! Опытным путем было установлено, что Visual Studio, что-то (скорее всего файлы *.props и *.targets) кеширует — поэтому простого Unload Project/Reload Project не достаточно! Именно поэтому я изначально создал sln файл рядом с vcxproj, чтобы было удобно перезапускать, не меняя директории.

Итак давайте просто удалим строчки с тегом Import и посмотрим, что получится.Файл должен быть таким:

test.vcxproj Debug Win32 Release Win32 {E1064D79-B415–4EDC-9FAC-C50E4102268B} test Открыв его в студии — мы с удивлением обнаружим, что файл все ещё является валидным. Но собрать его уже не получится, мы получим сообщение:1>Error: The «ConfigurationGeneral» rule is missing from the project.Пока мы не желаем собирать проект — поэтому не обращаем на это внимания.Посмотрим, что у нас в свойствах проекта — и наблюдаем следующую картину: 5f92c320ef17471ca45f554331334db9.pngКрасота! Мы убрали все лишнее, вернее всего, убрали вообще все.

Если мы попытаемся добавить файл к проекту, то у нас ничего не получится, студия выдает ошибку.79ec0ee68a254e7a9614719369906934.png

Лирическое отступление: Читатель возможно задаст вопрос, а как же студия все ещё определяет, что это проект C++ — элементарно по расширению vcxproj, которое явно указывает на Visual C++. Возможно читателю этот вопрос покажется достаточно глупым, но когда долго экспериментируешь с проектом, который фактически уже не является С++ проектом, а студия все ещё пытается вести себя по правилам С++, совершенно забываешь про расширение самого файла —, а оно имеет кардинальное значение. Мы будем от этого избавляться, но лишь в одной из следующих частей данного повествования.

Проект пуст. Приступим к наполнению. Читатель наверняка уже догадался, что мы будем создавать свои собственные файлы *.props и *.targets, но в начале немного теории: файлы *.props и *.targets совсем не обязательно должны иметь расширения *.props и *.targets — это фактически, что-то типа подключаемых файлов (include). Но мы не будем нарушать гегемонию студии и оставим привычные всем расширения. *.props обычно отвечают за свойства проекта и переменные окружения. *.targets отвечают за сборку — в них описывается, что делать с файлами (а может и не с файлами), которые добавлены к проекту, т.е. все возможные действия/задания (Tasks) и за типы файлов проекта определямые схемой (ProjectSchema) и правилами (Rules). Файл *.targets. Для дальнейшего повествования, я предлагаю перейти от абстракции к конкретной задаче прикручивания компилятора XC8 от компании Microchip к Visual Studio. Компилятор XC8 необходимо скачать с сайта Microchip и установить.Отступление: Вообще файлы *.targets как и *.props находятся в определенных папках самого MSBuild, но сейчас мы так делать не будем, т.к. это уже относится к задачам распространения новоиспеченного расширения и, в целом, задачам инсталятора, а для наших экспериментов удобнее всего все хранить в одной директории рядом с проектом.

Создадим файл XC8.targets в директории рядом с test.vcxproj.

Содержание XC8.targets:

Из файла видно, что мы пытаемся определить схему страницы свойств (PropertyPageSchema) и подключить файл XC.Items.xml. Как догадался читатель — в файле XC.Items.xml мы будем описывать типы файлов, которые будут участвовать в нашем проекте. Конкретно для компилятора XC8 — это файлы *.c; *.h; *.asm итд.

Создадим XC8.Items.xml поместив его в директорию с нашим проектом.Содержание XC8.Items.xml:

Как видно в этом файле мы определили те типы файлов, которые нам нужны в проекте. А с помощью тега FileExtension определили расширения этих файлов.Теперь наконец импортируем XC8.targets в наш проект, для этого используем тег ImportСодержание test.vcxproj:

Debug Win32 Release Win32 {E1064D79-B415–4EDC-9FAC-C50E4102268B} test Теперь открываем студию и пробуем добавить простейший файл main.cСодержание файла:

main.c #include

int main () { return 0; } Вуаля — теперь файл добавился. Посмотрим свойства файла: 1e02051b2be14d6fa71b68cf70870c56.pngМы видим, что в выпадающем списке Item Type отображаются элементы которые мы указали в файле XC8.Items.xml.Важно!: Основным тегом в файле XC8.Items.xml, в целом, является ContentType с атрибутом ItemType. Именно значение атрибута ItemType мы будем использовать в дальнейшем как основное.

Сохраним в студии наш test.vcxproj, закроем студию, а затем посмотрим его текст:

Debug Win32 Release Win32 {E1064D79-B415–4EDC-9FAC-C50E4102268B} test Мы видим, что у нас добавился ещё тег ItemGroup внутри, которого находится тег Compile. Название этого тега — это и есть то, что мы указали в качестве значения атрибута ItemType тега ContentType в файле XC8.Items.xml. Как уже догадался уважаемый читатель — с помощью этого механизма в наш проект включаются новые файлы для сборки.Теперь займемся свойствами проекта. Для этого необходимо в наш XC8.targets включить 2 файла:

Project PropertySheet И, как обычно, необходимо создать файлы XC8.General.xml и XC8.General.PS.xml в директории с проектом.Сразу оговорюсь: зачем нужен второй файл XC8.General.PS.xml я, до конца, так и не выяснил, т.к. он присутствовал во всех материалах, которые я изучал — я решил его оставить, назвав в соответствии с нашим проектом. Если кто-нибудь имеет информацию по данному вопросу — прошу поделиться.Содержимое:

XC8.General.PS.xml

Теперь обратимся к файлу XC8.General.xml в нем мы будем описывать свойства нашего проекта, а именно — страницу свойств General. Напоминаю: именно эту страницу от нас просила Visual Studio при попытке сборки проекта. Страницы свойств в проекте описываются тегом Rule. Без лишних слов я просто приведу свой файл XC8.General.xml, а затем попытаюсь объяснить его структуру.

Файл довольно объемный, но мне все же не хотелось убирать его под спойлер, т.к. он имеет важное значение.Итак, тег Rule имеет несколько важных атрибутов:

Name=«ConfigurationGeneral» — студия ищет правило именно с этим значением атрибута Name. Если у Вас не будет правила с названием ConfigurationGeneral, скорее всего у Вас ничего не получится. DisplayName=«General» — это название, которое будет отображаться в окне конфигурации проекта, можете назвать как угодно. Я оставил, канонически: «General». Description=«General» — описание. Пояснять надеюсь не требуется. SwitchPrefix=»-» — этот атрибут определяет префикс ключа команд при передаче их в систему сборки. Допустим ключи вашего компилятора начинаются с »/» (пример: /Ipath определяет путь подключаемых файлов) — соответсвенно значение этого атрибута будет »/», у XC8 компилятора все ключи начинаются с »-», что собственно у меня и написано. Если у вас ключи разных форматов, то этот атрибут можно оставить пустым или вовсе не указывать. xmlns — атрибут поведения документа в целом, пространства имен и т.д., соответсвенно менять его не имеет смысла. PageTemplate — определяет отображение страницы свойств в настройке проекта, мы будем работать с шаблонами generiic и tool — всю разницу между ними я покажу позже на скриншотах (уже в следующей части). Тег Rule.Categories служит для определения категорий внутри страницы свойств. Таких категорий у нас две: General — общие настройки проекта — обычно всякие пути. ProjectDefaults — настройки особенностей нашего XC8 проекта — я вынес сюда ключики, которые должны передаваться и компилятору и к линковщику. Понятно, что Вы можете определить таких категорий сколько пожелаете.Тег Rule.DataSource определяет где будут храниться значения свойств. У нас указано ProjectFile — т.е. в файле проекта. Я атрибут Persistence не менял, т.к. не представляю где ещё могут храниться настройки проекта как не в файле этого самого проекта, в нашем случае в test.vcxproj.

Теги с окончанием Property, как Вы уже догадались — это и есть свойства которые будут у нас отображаться в окне свойств нашего проекта.

StringProperty — строковое. StringListProperty — список строк. Например перечисление Include директорий или, как здесь, перечесление файлов при очистке проекта (Clean). IntProperty — числовое, но оно ведет себя почему-то как строковое, так что этот момент остается загадкой. BoolProperty — флаг. EnumProperty — перечисление, я расскажу о нем позже т.к. в нашем файле XC8.General.xml таких нет. Рассмотрим основные атрибуты свойств:

Name — название. Имеет важное значение т.к. может участвовать в объявлении макросов студии (те которые $(BlaBlaBla)). Как это сделать я расскажу позже. DisplayName — отображаемое название. Description — описание. Отображается внизу — при выборе данного свойства в окне свойств проекта. Category — определяет, в какой категории будет отображаться наше свойство. В нашем случае категорий 2: General и ProjectDefaults. Subtype — определяет подтип свойства StringProperty. Из тех, которые я видел — это folder и file, но поведение их почти ничем не отличается. F1Keyword — определяет справку по данному свойству. Default — этот атрибут по идее должен определять значение свойства по умолчанию, но он этого не делает — возможно, это просто подсказка (которую я тоже нигде не нашел). Для определения значений свойств по умолчанию существует совершенно другой механизм, о котором я буду рассказывать в следующей части повествования. Switch — определяет ключ данного свойства. В конечном итоге складывается с атрибутом SwitchPrefix тега Rule и передается в сборку. В случае со свойством QuietMode это будет »-Q» Атрибутов у свойств и правил достаточно много — читатель может познакомится с ними по ссылке: Microsoft.Build.Framework.XamlTypes Namespace. Но будьте готовы не найти там никакого внятного описания, такое ощущение, что данная документация сделана автоматически каким-то генератором, без описаний. Радует только то, что назначение многих атрибутов понятно по их названиям.

Теперь открываем наш проект в студии и смотрим окно свойств проектаbcbafd93eaa749fcbfc885745f5c9b54.pngЕсли попытаться собрать проект, то мы увидим уже другую ошибку: error MSB4057: The target «build» does not exist in the project., которая говорит нам об отсутствии Target тега с названием build.

На этом первую часть повествования я заканчиваю, мы добились определенного результата. В следующий раз я расскажу, как создавать Target, Task, определять значения по умолчанию и макросы студии.

Файлы проекта можно скачать здесь.

Материалы, которые позволили мне изучить вопрос: MSDN: Microsoft.Build.Framework.XamlTypes NamespaceПроект: vs-androidСтатья на Хабре: Минимальный проект MsBuildMSDN: Пошаговое руководство. Создание файла проекта MSBuild с нуляА также метод научного тыка в директории С:\Program Files\MSBuild\Microsoft.Cpp\v4.0\

© Habrahabr.ru