Visual Studio Extensibility. Part One: MSBuild

  • Tutorial
Hi Habr, in these articles I will try to cover the topic of Microsoft Visual Studio extensions (and incidentally also MSBuild), because this area is extremely poorly documented and generally covered in a veil of some mystery.



Prologue


I am a professional C ++ developer with quite a lot of experience, as well as a big fan of Microsoft products and, first of all, my main development tool - Visual Studio. Not so long ago, as a hobby, I started programming microcontrollers, and I chose microcontrollers from Microchip. The only thing that didn’t suit me was the use of development tools provided by Microchip itself. I can’t say anything bad about these products, I just don’t want to install several IDEs on my working (or home) computer, so the idea was born to integrate the Microchip XC8 compiler into Microsoft Visual Studio. Later, I saw one more plus in this idea - a lot of my (and not only my) projects are directly or indirectly related to connecting them to a computer, so I have to develop a response software part - it would be great to combine them into one solution (solution) with a firmware project . After digging through a large amount of time, I realized that the topic of integrating something in Visual Studio is a kind of white spot: there is no normal description, scanty descriptions on blogs, and that the worst is that there are practically no examples. Having collected some information bit by bit, having understood something from the description and based on the method of scientific poking, I decided to share the knowledge I gained. So let's go. After digging through a large amount of time, I realized that the topic of integrating something in Visual Studio is a kind of white spot: there is no normal description, scanty descriptions on blogs, and that the worst is that there are practically no examples. Having collected some information bit by bit, having understood something from the description and based on the method of scientific poking, I decided to share the knowledge I gained. So let's go. After digging through a large amount of time, I realized that the topic of integrating something in Visual Studio is a kind of white spot: there is no normal description, scanty descriptions on blogs, and that the worst is that there are practically no examples. Having collected some information bit by bit, having understood something from the description and based on the method of scientific poking, I decided to share the knowledge I gained. So let's go.

Action plan


Well, since we decided to expand the functionality of Visual Studio by screwing a third-party compiler, then let's expand it as much as possible. To do this, define a list of what we want to do:

  1. That when viewing properties of the project (Project Properties) in Visual Studio - there would be our own properties.
  2. Define your set of file extensions that could be included in the project.
  3. It is natural to determine your build system so that our compilers, linkers, etc. are called.
  4. Create your own type of project so that the studio can open a file with our own extension.
  5. Create a wizard to generate this type of project.


Well, let's try to implement at least part of the requirements from this list.

Part 1: MSBuild


Let's start with the theory:
  • Microsoft Visual Studio - A little less than fully built on COM technology. Therefore, you need to be prepared to face it. Although we will try to avoid this.
  • Any Microsoft Visual Studio project is an MSBuild script.


In view of the above, in order to expand the capabilities of the studio, first of all we will have to study the assembly system device, called MSBuild.

I’ll make a reservation right away: I had quite old VIsual Studio 2010 at hand - so the whole description will be for her, but I believe that from 2012 and 2013 everything will be the same in studios.

So we open the studio, create the Blank Solution and add the Empty Project to it (I prefer the project in the C ++ category, so I selected it)


I called the project and the solution “test” and made them lie in the same directory (this is convenient for our experiments, I will explain later) - thus obtaining the files test.sln and test.vcxproj

Now we close the studio and take some kind of text editor (it is better with XML syntax highlighting - in principle, the same studio will be suitable, only its other instance) and open test.vcxproj as a text file.

Let's see what's inside test.vcxproj :
test.vcxproj
DebugWin32ReleaseWin32{E1064D79-B415-4EDC-9FAC-C50E4102268B}testApplicationtrueMultiByteApplicationfalsetrueMultiByteLevel3DisabledtrueLevel3MaxSpeedtruetruetruetruetrue


The most important thing to see here:
  • ProjectConfiguration tag .
  • ProjectGuid tag .
  • files that are connected with the Import tag .

Everything else can be safely deleted. I also suggest deleting the test.filters file so that virtual directories in the project do not interfere with us.
DebugWin32ReleaseWin32{E1064D79-B415-4EDC-9FAC-C50E4102268B}test

The above file is absolutely valid from the point of view of the studio and MSBuild, it can be opened and even assembled.

I also suggest immediately dealing with the assembly of our project from the command line:
  1. Launch Microsoft Visual Studio Command Prompt
  2. Go to the directory where test.vcxproj is located
  3. We execute msbuild test.vcxproj / p: Configuration = Debug / p: Platform = Win32

You can write a script to automate this process, so that it is more convenient to check the assembly for errors and other things.

Now we begin to understand what is responsible for the properties of the project and in general what makes our project a studio project. And all the lines do this:

My imported files are located in the C: \ Program Files \ MSBuild \ Microsoft.Cpp \ v4.0 directory. Of

course, you can try to look at them, but I'm afraid a person who has never dealt with MSBuild will hardly understand anything, but the desire to understand in this everything will instantly disappear. So I suggest not to look there yet, because you can spend a lot of time (as the author did) and still not understand anything.

Important !!: Each time you edit vcxproj and related files, you must restart the studio completely! It was experimentally found that Visual Studio caches something (most likely * .props and * .targets files) - so a simple Unload Project / Reload Project is not enough! That is why I originally created a sln file next to vcxprojso that it is convenient to restart without changing directories.

So let's just delete the lines with the Import tag and see what happens.
The file should be like this:
test.vcxproj
DebugWin32ReleaseWin32{E1064D79-B415-4EDC-9FAC-C50E4102268B}test


Opening it in the studio, we are surprised to find that the file is still valid. But collecting it no longer works, we get the message:
1> Error: The "ConfigurationGeneral" rule is missing from the project.
While we do not want to collect the project - therefore, we do not pay attention to it.

Let's see what we have in the project properties - and observe the following picture:

Beauty! We removed everything superfluous, or rather, removed everything in general.

If we try to add a file to the project, then we will not succeed, the studio gives an error.


Lyrical digression :
The reader may ask a question, but how does the studio still determine that this is a C ++ project - elementary by extension vcxprojwhich explicitly points to Visual C ++. Perhaps this question will seem rather silly to the reader, but when you experiment for a long time with a project that is actually not a C ++ project, and the studio is still trying to behave according to the C ++ rules, you completely forget about the file extension itself - and it has cardinal significance . We will get rid of this, but only in one of the following parts of this story.

The project is empty. Let's start filling.


The reader has probably already guessed that we will create our own * .props and * .targets files , but at the beginning there is a bit of theory:
  • files * .props and * .targets does not necessarily have to have extensions * .props and * .targets - it actually, something like include files (include). But we will not violate the hegemony of the studio and leave the extensions familiar to everyone.
  • * .props are usually responsible for project properties and environment variables.
  • * .targets are responsible for the assembly - they describe what to do with files (or maybe not with files) that are added to the project, i.e. all possible actions / tasks (Tasks) and for the types of project files defined by the scheme (ProjectSchema) and the rules (Rules).


File * .targets.


For further narration, I propose moving from abstraction to the specific task of screwing the XC8 compiler from Microchip to Visual Studio. The XC8 compiler must be downloaded from the Microchip website and installed.

Digression : In general, the * .targets files, like the * .props, are located in certain folders of MSBuild itself, but now we will not do this, because this already applies to the tasks of distributing the newly made extension and, in general, the tasks of the installer, and for our experiments it is most convenient to store everything in one directory next to the project.

Create an XC8.targets file in the directory next to test.vcxproj . XC8.targets

content :


It can be seen from the file that we are trying to determine the scheme of the property page (PropertyPageSchema) and include the XC8.Items.xml file . As the reader guessed, in the XC8.Items.xml file we will describe the types of files that will participate in our project. Specifically for the XC8 compiler, these are * .c files ; * .h; * .asm etc.

Let's create XC8.Items.xml by placing it in the directory with our project. XC8.Items.xml
content :

As you can see in this file, we determined the types of files that we need in the project. And with the help of the FileExtension tag, we determined the extensions of these files.

Now finally import the XC8.targets into our project, for this we use the Import tag
Content test.vcxproj :
DebugWin32ReleaseWin32{E1064D79-B415-4EDC-9FAC-C50E4102268B}test


Now we open the studio and try to add the simplest file main.c
File content:
main.c
#include 
int main()
{
	return 0;
}


Voila - now the file has been added. Let's look at the properties of the file:

We see that the Item Type drop-down list displays the elements that we specified in the XC8.Items.xml file .

Important !!: The main tag in the XC8.Items.xml file, in general, is ContentType with the ItemType attribute . It is the value of the ItemType attribute that we will use in the future as the main one.

Let's save our test.vcxproj in the studio , close the studio, and then look at its text:
DebugWin32ReleaseWin32{E1064D79-B415-4EDC-9FAC-C50E4102268B}test

We see that we have added another ItemGroup tag inside, which is the Compile tag . The name of the tag - this is what we have indicated as the value attribute ItemType tag ContentType file XC8.Items.xml . As the dear reader already guessed, using this mechanism, new files for assembly are included in our project.

Now let's deal with the project properties. To do this, we need to include 2 files in our XC8.targets :
ProjectPropertySheet

And, as usual, you need to create the XC8.General.xml and XC8.General.PS.xml files in the project directory. I’ll

make a reservation right away : why do I need a second XC8.General.PS.xml file , I did not find out until the end, because he was present in all the materials that I studied - I decided to leave him, naming in accordance with our project. If anyone has information on this issue - please share.
Content:
XC8.General.PS.xml



Now we turn to the XC8.General.xml file in it we will describe the properties of our project, namely, the General properties page . I remind you: it was this page that Visual Studio requested from us when trying to build the project. Property pages in a project are described by the Rule tag . Without further ado, I will just give my XC8.General.xml file and then try to explain its structure.

The file is quite voluminous, but I still did not want to put it under the spoiler, because he is important.

So, the Rule tag has several important attributes:
  • Name = “ConfigurationGeneral” - the studio is looking for a rule with this Name attribute value. If you do not have a rule named ConfigurationGeneral, most likely you will not succeed.
  • DisplayName = "General" is the name that will be displayed in the project configuration window, you can name it whatever you like. I left, canonically: "General."
  • Description = "General" - description. I hope I do not need to explain.
  • SwitchPrefix = "-" - this attribute defines the command key prefix when transferring them to the assembly system. Suppose your compiler keys start with "/" (example: / Ipath defines the path of the files to be connected) - the value of this attribute will be "/", for the XC8 compiler, all keys start with "-", which is actually written for me. If you have keys of different formats, then this attribute can be left blank or not specified at all.
  • xmlns - an attribute of the behavior of the document as a whole, namespace, etc., changing it accordingly does not make sense.
  • PageTemplate - defines the display of the property page in the project settings, we will work with the generiic and tool templates - I will show all the difference between them later in the screenshots (in the next part).

The Rule.Categories tag is used to define categories within the property page. We have two categories:
  • General - general project settings - usually all sorts of ways.
  • ProjectDefaults - settings for the features of our XC8 project - I brought out the keys here, which should be transmitted to both the compiler and the linker.

It is clear that you can define such categories as you wish.

The Rule.DataSource tag determines where property values ​​will be stored. We have ProjectFile specified - i.e. in the project file. I did not change the Persistence attribute , because I can’t imagine where else the project settings can be stored if not in the file of this project itself, in our case in test.vcxproj .

Tags with the end of Property, as you may have guessed, these are the properties that will be displayed in the properties window of our project.
  • StringProperty is a string.
  • StringListProperty is a list of strings. For example, listing the Include directory or, as here, listing the files when cleaning the project (Clean).
  • IntProperty is numeric, but for some reason it behaves like a string, so this moment remains a mystery.
  • BoolProperty - flag.
  • EnumProperty is an enumeration, I will talk about it later because in our file XC8.General.xml there are none.


Consider the basic attributes of properties:
  • Name - the name. It is important because may participate in studio macro declarations (those with $ (BlaBlaBla) ). How to do this I will tell later.
  • DisplayName is the display name.
  • Description - description. It is displayed below - when this property is selected in the project properties window.
  • Category - determines in which category our property will be displayed. In our case, categories 2: General and ProjectDefaults .
  • Subtype - Defines a subtype of the StringProperty property. Of those that I saw - this is folder and file , but their behavior is almost no different.
  • F1Keyword - defines help for this property.
  • Default - this attribute should in principle determine the value of the property by default, but it does not do this - perhaps this is just a hint (which I also have not found anywhere) . There is a completely different mechanism for defining property values ​​by default, which I will discuss in the next part of the story.
  • Switch - defines the key of this property. Eventually added to the attribute SwitchPrefix tag Rule and transferred to the assembly. In the case of the QuietMode property , this will be "-Q"


There are a lot of attributes for properties and rules - the reader can get acquainted with them at the link: Microsoft.Build.Framework.XamlTypes Namespace . But be prepared not to find any intelligible description there, it seems that this documentation was made automatically by some generator, without descriptions. The good news is that the purpose of many attributes is clear from their names.

Now open our project in the studio and look at the project properties window.

If we try to build the project, we will see another error: error MSB4057: The target “build” does not exist in the project. , which tells us that there is no Target tag called build .

On this, the first part of the narrative, I finish, we have achieved a certain result. Next time I will tell you how to create Target , Task , define default values ​​and studio macros.

Project files can be downloaded here.

Materials that allowed me to study the issue:


MSDN: Microsoft.Build.Framework.XamlTypes Namespace
Project: vs-android
Article on Habr: Minimal project MsBuild
MSDN: Walkthrough. Creating an MSBuild project file from scratch
. Also a scientific poking method in the C: \ Program Files \ MSBuild \ Microsoft.Cpp \ v4.0 \ directory

Also popular now: