📜 ⬆️ ⬇️

Visual Studio Extensibility. Part One: MSBuild

Hi Habr, in these articles I will try to highlight the theme of Microsoft Visual Studio extensions (and also MSBuild), because This area is extremely poorly documented and generally covered with a veil of some mysteriousness.



Prologue


I am a professional C ++ developer with quite a lot of experience, as well as a big fan of Microsoft products and, above 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 did not 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 multiple IDEs on my work (or home) computer, so the thought was born of integrating the XC8 compiler from Microchip into Microsoft Visual Studio. Later I saw another plus in this idea - many of my (and not only my) projects are directly or indirectly connected 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 with the firmware project . Having picked up a large amount of time, I realized that the topic of integrating something in Visual Studio is such a white spot: there is no normal description, few descriptions on blogs, and the worst is that there are practically no examples. Having collected some information, bit by bit, having understood something from the description and relying on the method of scientific typing, I decided to share the acquired knowledge. 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, determine the list of what we want to do:
')
  1. So that when viewing Project Properties in Visual Studio, we’ll have our own properties.
  2. Determine your own 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 project type so that the studio can open the file with our own extension.
  5. Create a wizard to generate this type of project.


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

Part 1: MSBuild


Let's start with the theory:


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

Immediately make a reservation: I had an old VIsual Studio 2010 on hand — so the whole description would be for her, but I believe that from 2012 and 2013 studios everything will be the same.

So open a studio, create a Blank Solution and add an Empty Project to it (the project in the C ++ category is closer to me, so I chose it)


I called both 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 getting the test.sln and test.vcxproj files

Now we close the studio and take some kind of text editor (better with XML syntax highlighting - basically the same studio will suit, just another copy of it) and open test.vcxproj as a text file.

Let's see what test.vcxproj has inside:
test.vcxproj
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <ProjectGuid>{E1064D79-B415-4EDC-9FAC-C50E4102268B}</ProjectGuid> <RootNamespace>test</RootNamespace> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <CharacterSet>MultiByte</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>MultiByte</CharacterSet> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup /> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> </Link> </ItemDefinitionGroup> <ItemGroup> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> </Project> 


The most important thing to see here is:

Everything else can be safely removed. I also suggest deleting the test.filters file so that the virtual directories in the project do not interfere with us.
 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <ProjectGuid>{E1064D79-B415-4EDC-9FAC-C50E4102268B}</ProjectGuid> <RootNamespace>test</RootNamespace> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> </Project> 

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 to immediately deal with the build of our project from the command line:
  1. We start Microsoft Visual Studio Command Prompt
  2. Go to the directory where test.vcxproj is located
  3. Run 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 - the project of the studio. And do it all the lines:
 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> 

I import files from the directory C: \ Program Files \ MSBuild \ Microsoft.Cpp \ v4.0

You can certainly try to see them, but I'm afraid a person who has never understood MSBuild will hardly understand something, and the desire to understand this will disappear instantly. So I suggest not to look there yet, because you can spend a lot of time (as the author did) and still do not understand anything.

Important !!: With every edit of vcxproj and related files, you must restart the studio completely! It was experimentally established that Visual Studio is caching something (most likely * .props and * .targets files) - therefore, a simple Unload Project / Reload Project is not enough! That is why I initially created a sln file next to vcxproj , so that it would be convenient to restart without changing the directory.

So let's just delete the lines with the Import tag and see what happens.
The file should be like this:
test.vcxproj
 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <ProjectGuid>{E1064D79-B415-4EDC-9FAC-C50E4102268B}</ProjectGuid> <RootNamespace>test</RootNamespace> </PropertyGroup> </Project> 


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

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

Beauty! We have removed all unnecessary, most likely, removed everything.

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


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

The project is empty. Proceed to the filling.


The reader probably already guessed that we will create our own * .props and * .targets files , but in the beginning there is a bit of theory:


File * .targets.


For further narration, I propose to move 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 as well as the * .props are in certain folders of MSBuild itself, but for now we will not do that, because this already refers to the tasks of the distribution of the new extension and, in general, to the installer tasks, 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:
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Properties --> <ItemGroup> <PropertyPageSchema Include="XC8.Items.xml"/> </ItemGroup> </Project> 


From the file it is clear that we are trying to determine the schema of the property page (PropertyPageSchema) and include the file XC8.Items.xml . 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 and so on.

Create XC8.Items.xml by placing it in the directory with our project.
Content XC8.Items.xml :
 <?xml version="1.0" encoding="utf-8"?> <ProjectSchemaDefinitions xmlns="http://schemas.microsoft.com/build/2009/properties"> <ContentType Name="CCode" DisplayName="C Code" ItemType="Compile"> </ContentType> <ContentType Name="AsmCode" DisplayName="Asm Code" ItemType="Compile"> </ContentType> <ContentType Name="Header" DisplayName="C/C++ Header" ItemType="Include"> </ContentType> <ContentType Name="Text" DisplayName="Text file" ItemType="None"> </ContentType> <ItemType Name="Compile" DisplayName="XC8 Compiler"/> <ItemType Name="Include" DisplayName="C header"/> <ItemType Name="None" DisplayName="Does not participate in build"/> <FileExtension Name=".c" ContentType="CCode"/> <FileExtension Name=".cc" ContentType="CCode"/> <FileExtension Name=".s" ContentType="AsmCode"/> <FileExtension Name=".asm" ContentType="AsmCode"/> <FileExtension Name=".h" ContentType="Header"/> <FileExtension Name=".txt" ContentType="Text"/> </ProjectSchemaDefinitions> 

As you can see in this file, we have defined the types of files that we need in the project. And using the FileExtension tag, we identified the extensions of these files.

Now, finally, import XC8.targets into our project, for this we use the Import tag.
Contents test.vcxproj :
 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <ProjectGuid>{E1064D79-B415-4EDC-9FAC-C50E4102268B}</ProjectGuid> <RootNamespace>test</RootNamespace> </PropertyGroup> <Import Project="XC8.targets" /> </Project> 


Now open the studio and try to add the simplest file main.c
File Contents:
main.c
 #include <xc.h> int main() { return 0; } 


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

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

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 see its text:
 <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup Label="ProjectConfigurations"> <ProjectConfiguration Include="Debug|Win32"> <Configuration>Debug</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|Win32"> <Configuration>Release</Configuration> <Platform>Win32</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <ProjectGuid>{E1064D79-B415-4EDC-9FAC-C50E4102268B}</ProjectGuid> <RootNamespace>test</RootNamespace> </PropertyGroup> <ItemGroup> <Compile Include="main.c" /> </ItemGroup> <Import Project="XC8.targets" /> </Project> 

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

Now we will deal with the project properties. For this you need to include 2 files in our XC8.targets :
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!-- Properties --> <ItemGroup> <PropertyPageSchema Include="XC8.Items.xml"/> <PropertyPageSchema Include="XC8.General.xml"> <Context>Project</Context> </PropertyPageSchema> <PropertyPageSchema Include="XC8.General.PS.xml"> <Context>PropertySheet</Context> </PropertyPageSchema> </ItemGroup> </Project> 

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

Immediately make a reservation: why do I need the second XC8.General.PS.xml file , I, until the end, did not find out, because He was present in all the materials that I studied - I decided to leave him, calling in accordance with our project. If someone has information on this issue - please share.
Content:
XC8.General.PS.xml
 <?xml version="1.0" encoding="utf-8"?> <Rule Name="ConfigurationGeneral" DisplayName="General" PageTemplate="generic" Description="General" xmlns="http://schemas.microsoft.com/build/2009/properties"> <Rule.Categories> <Category Name="General" DisplayName="General" Description="General" /> </Rule.Categories> <Rule.DataSource> <DataSource Persistence="ProjectFile" Label="Configuration" /> </Rule.DataSource> </Rule> 



Now let's 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 asked us for when trying to build a project. Property pages in a project are described by a Rule tag. Without further ado, I’ll just bring in my XC8.General.xml file and then try to explain its structure.
 <?xml version="1.0" encoding="utf-8"?> <Rule Name="ConfigurationGeneral" DisplayName="General" PageTemplate="generic" Description="General" SwitchPrefix="-" xmlns="http://schemas.microsoft.com/build/2009/properties"> <Rule.Categories> <Category Name="General" DisplayName="General" Description="General" /> <Category Name="ProjectDefaults" DisplayName="Project Defaults" Description="Project Defaults" /> </Rule.Categories> <Rule.DataSource> <DataSource Persistence="ProjectFile" Label="Configuration" /> </Rule.DataSource> <!-- General --> <StringProperty Name="OutDir" DisplayName="Output Directory" Description="Specifies a relative path to the output file directory; can include environment variables." Category="General" Default="$(SolutionDir)$(Configuration)\" Subtype="folder" F1Keyword="VC.Project.VCConfiguration.OutputDirectory"> <StringProperty.DataSource> <DataSource Persistence="ProjectFile" Label="" /> </StringProperty.DataSource> </StringProperty> <StringProperty Name="IntDir" DisplayName="Intermediate Directory" Description="Specifies a relative path to the intermediate file directory; can include environment variables." Category="General" Default="$(Configuration)\" Subtype="folder" F1Keyword="VC.Project.VCConfiguration.IntermediateDirectory"> <StringProperty.DataSource> <DataSource Persistence="ProjectFile" Label="" /> </StringProperty.DataSource> </StringProperty> <StringProperty Name="TargetName" DisplayName="Target Name" Description="Specifies a file name that this project will generate." F1Keyword="VC.Project.VCConfiguration.TargetName" Category="General" Default="$(ProjectName)" > <StringProperty.DataSource> <DataSource Persistence="ProjectFile" Label="" /> </StringProperty.DataSource> </StringProperty> <StringListProperty Name="ExtensionsToDeleteOnClean" DisplayName="Extensions to Delete on Clean" Description="Semi-colon delimited wildcard specification for which files in the intermediate directory to delete on clean or rebuild." Category="General" Separator=";" F1Keyword="VC.Project.VCConfiguration.DeleteExtensionsOnClean"> <StringListProperty.DataSource> <DataSource Persistence="ProjectFile" Label="" /> </StringListProperty.DataSource> </StringListProperty> <StringProperty Name="BuildLogFile" DisplayName="Build Log File" Description="Specifies the build log file to write to when build logging is enabled." F1Keyword="VC.Project.VCConfiguration.BuildLogFile" Category="General" Default="$(ProjectDir)\$(MSBuildProjectName).log" Subtype="file"> <StringProperty.DataSource> <DataSource Persistence="ProjectFile" ItemType="BuildLog" PersistedName="Path" /> </StringProperty.DataSource> </StringProperty> <StringProperty Name="Chip" DisplayName="Select Processor" Description="This option selects the processor target for compilation. A list of available processors can be obtained by using the chipinfo option." Category="ProjectDefaults" Switch="-chip="/> <BoolProperty Name="QuietMode" DisplayName="Quiet Mode" Description="This option suppresses the Microchip copyright message, and any summary information usually printed by the compiler." Category="ProjectDefaults" Default="false" Switch="Q"/> <BoolProperty Name="VerboseMode" DisplayName="Verbose Mode" Description="This option is used to display the command lines used to invoke each of the compiler passes." Category="ProjectDefaults" Default="false" Switch="V"/> </Rule> 

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

So, the Rule tag has several important attributes:

The Rule.Categories tag is used to define categories within the property page. We have two such categories:

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

The Rule.DataSource tag defines 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 as not in the file of this project itself, in our case in test.vcxproj .

Tags with the end of Property, as you already guessed - these are the properties that will be displayed in the properties window of our project.


Consider the basic attributes of properties:


There are a lot of attributes and properties of the attributes - the reader can get acquainted with them by the link: Microsoft.Build.Framework.XamlTypes Namespace . But be prepared not to find there any intelligible description, such a feeling that this documentation was made automatically by some kind of generator, without descriptions. The only good news is that the assignment of many attributes is understandable by their names.

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

If we try to build a project, we will see another error: error MSB4057: The target “build” does not exist in the project. which tells us about the absence of a Target tag called build .

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

Project files can be downloaded here.

Materials that allowed me to study the question:


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

Source: https://habr.com/ru/post/251795/


All Articles