Visual Studio Extensibility. Часть первая: MSBuild
Привет Хабр, в этих статьях я попытаюсь осветить тему расширений Microsoft Visual Studio (а попутно ещё и MSBuild), т.к. эта сфера является крайне плохо документированной и вообще покрыта пеленой какой-то загадочности.
ПрологЯ являюсь профессиональным разработчиком 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 (мне ближе проект в категории С++, поэтому я выбрал его)
Я назвал и проект и решение «test» и сделал так, чтобы они лежали в одной директории (это удобно для наших экспериментов, объясню позже)- таким образом получив файлы test.sln и test.vcxproj
Теперь закрываем студию и берем какой нибудь текстовый редактор (лучше с подсветкой XML синтаксиса — в принципе подойдет та же студия, только уже другой её экземпляр) и открываем test.vcxproj как текстовый файл.
Посмотрим, что внутри у test.vcxproj:
test.vcxproj
Запускаем Microsoft Visual Studio Command Prompt Переходим в директорию, где находится test.vcxproj Выполняем msbuild test.vcxproj /p: Configuration=Debug /p: Platform=Win32 Можно написать скрипт для автоматизации данного процесса, чтобы было удобнее проверять сборку на предмет ошибок и прочего.Теперь начинаем разбираться в том, что отвечает за свойства проекта и вообще, что делает наш проект — проектом студии. А делают это все строчки:
Важно!: При каждой правке vcxproj и сопутствующих файлов необходимо перезапускать студию полностью! Опытным путем было установлено, что Visual Studio, что-то (скорее всего файлы *.props и *.targets) кеширует — поэтому простого Unload Project/Reload Project не достаточно! Именно поэтому я изначально создал sln файл рядом с vcxproj, чтобы было удобно перезапускать, не меняя директории.
Итак давайте просто удалим строчки с тегом Import и посмотрим, что получится.Файл должен быть таким:
test.vcxproj
Если мы попытаемся добавить файл к проекту, то у нас ничего не получится, студия выдает ошибку.
Лирическое отступление: Читатель возможно задаст вопрос, а как же студия все ещё определяет, что это проект 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:
Создадим XC8.Items.xml поместив его в директорию с нашим проектом.Содержание XC8.Items.xml:
main.c
#include
int main () { return 0; } Вуаля — теперь файл добавился. Посмотрим свойства файла: Мы видим, что в выпадающем списке Item Type отображаются элементы которые мы указали в файле XC8.Items.xml.Важно!: Основным тегом в файле XC8.Items.xml, в целом, является ContentType с атрибутом ItemType. Именно значение атрибута ItemType мы будем использовать в дальнейшем как основное.
Сохраним в студии наш test.vcxproj, закроем студию, а затем посмотрим его текст:
XC8.General.PS.xml
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. Но будьте готовы не найти там никакого внятного описания, такое ощущение, что данная документация сделана автоматически каким-то генератором, без описаний. Радует только то, что назначение многих атрибутов понятно по их названиям.
Теперь открываем наш проект в студии и смотрим окно свойств проектаЕсли попытаться собрать проект, то мы увидим уже другую ошибку: 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\