Dieser Artikel ist eine Anleitung zum Anpassen der Assembly von C ++ Visual Studio-Projekten. Teilweise wurde es aus Materialien verstreuter Artikel zu diesem Thema reduziert, teilweise ist es das Ergebnis des Reverse Engineerings von Standard-Studio-Konfigurationsdateien. Ich habe es hauptsächlich geschrieben, weil die Nützlichkeit der Dokumentation von Microsoft selbst zu diesem Thema gegen Null geht, und ich wollte eine praktische Referenz zur Hand haben, auf die später zugegriffen und an andere Entwickler gesendet werden kann. Visual Studio verfügt über praktische und umfangreiche Funktionen zum Einrichten einer wirklich praktischen Arbeit mit komplexen Projekten, und ich bin verärgert zu sehen, dass diese Funktionen aufgrund der widerlichen Dokumentation derzeit nur sehr selten verwendet werden.
Versuchen wir beispielsweise, das Hinzufügen eines Flatbuffer-Schemas zum Studio zu ermöglichen. Das Studio ruft bei Bedarf automatisch flatc auf (und hat es nicht aufgerufen, wenn keine Änderungen vorgenommen wurden), und Sie können die Einstellungen direkt über die Dateieigenschaften festlegen

Inhaltsverzeichnis
* Stufe 1: Steigen Sie in .vcxproj-Dateien einSprechen Sie über .props-DateienAber warum sogar .vcxproj und .props trennen?Die Projekteinstellungen besser lesbar machenWir machen es einfach, Bibliotheken von Drittanbietern zu verbindenProjektvorlagen - Automatisieren Sie die Erstellung von Projekten* Stufe 2: Benutzerdefinierte benutzerdefinierte KompilierungTraditioneller AnsatzErfüllen Sie die MSBuild-ZieleVersuchen wir, ein Ziel zum Erstellen von .proto-Dateien zu erstellenWir erinnern an unser ModellbeispielU2DCheck- und tlog-DateienFinalisieren Sie unser benutzerdefiniertes ZielWas ist mit CustomBuildStep?Richtiges Kopieren von Dateien* Stufe 3: Integration in die GUI von Visual StudioWir ziehen die Einstellungen aus den Eingeweiden von .vcxproj in den Konfigurationseigenschaften herausErklären Sie Studios neue DateitypenVerknüpfen Sie Einstellungen mit einzelnen Dateien* Stufe 4: Erweiterung der Funktionalität von MSBuildHINWEIS: Alle Beispiele in diesem Artikel wurden in VS 2017 getestet. Nach meinem Verständnis sollten sie in früheren Versionen des Studios ab VS 2012 funktionieren, aber ich kann dies nicht versprechen.
Stufe 1: Steigen Sie in .vcxproj-Dateien ein
Werfen wir einen Blick in ein typisches .vcxproj-automatisch generiertes Visual Studio.
Es wird ungefähr so aussehen<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.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> <ProjectConfiguration Include="Debug|x64"> <Configuration>Debug</Configuration> <Platform>x64</Platform> </ProjectConfiguration> <ProjectConfiguration Include="Release|x64"> <Configuration>Release</Configuration> <Platform>x64</Platform> </ProjectConfiguration> </ItemGroup> <PropertyGroup Label="Globals"> <VCProjectVersion>15.0</VCProjectVersion> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> <Keyword>Win32Proj</Keyword> <RootNamespace>protobuftest</RootNamespace> <WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>true</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> <ConfigurationType>Application</ConfigurationType> <UseDebugLibraries>false</UseDebugLibraries> <PlatformToolset>v141</PlatformToolset> <WholeProgramOptimization>true</WholeProgramOptimization> <CharacterSet>Unicode</CharacterSet> </PropertyGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> </ImportGroup> <ImportGroup Label="Shared"> </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> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <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|x64'"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> <PropertyGroup Label="UserMacros" /> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <LinkIncremental>true</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <LinkIncremental>true</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <LinkIncremental>false</LinkIncremental> </PropertyGroup> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <LinkIncremental>false</LinkIncremental> </PropertyGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> <WarningLevel>Level3</WarningLevel> <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="pch.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> </ItemGroup> <ItemGroup> <Text Include="test.proto" /> </ItemGroup> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <ImportGroup Label="ExtensionTargets"> </ImportGroup> </Project>
Ziemlich unlesbares Durcheinander, nicht wahr? Und dies ist immer noch eine sehr kleine und fast triviale Datei. Versuchen wir, daraus etwas Lesbareres und Bequemeres für die Wahrnehmung zu machen.
Sprechen Sie über .props-Dateien
Beachten Sie dazu, dass es sich bei der von uns aufgenommenen Datei um ein gewöhnliches XML-Dokument handelt, das logisch in zwei Teile unterteilt werden kann, von denen der erste die Projekteinstellungen auflistet und der zweite die darin enthaltenen Dateien enthält. Lassen Sie uns diese Hälften physisch trennen. Dazu benötigen wir das Import-Tag, das bereits im Code enthalten ist. Dies ist ein Analogon zum #include und ermöglicht es Ihnen, eine Datei in eine andere aufzunehmen. Wir kopieren unsere .vcxproj in eine andere Datei und entfernen alle Deklarationen, die sich auf die im Projekt enthaltenen Dateien beziehen, und entfernen aus der .vcxproj wiederum alles
außer den Deklarationen, die sich auf die tatsächlich im Projekt enthaltenen Dateien beziehen. Die resultierende Datei mit den Projekteinstellungen, jedoch ohne Dateien in Visual Studio, wird normalerweise als Eigenschaftenblätter bezeichnet und mit der Erweiterung .props gespeichert. In .vcxproj liefern wir wiederum den entsprechenden Import
Jetzt beschreibt .vcxproj nur die im Projekt enthaltenen Dateien und ist viel einfacher zu lesen <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="settings.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> </PropertyGroup> <ItemGroup> <ClInclude Include="pch.h" /> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> </ItemGroup> <ItemGroup> <Text Include="test.proto" /> </ItemGroup> </Project>
Es kann noch weiter vereinfacht werden, indem unnötige XML-Elemente entfernt werden. Beispielsweise wird die Eigenschaft "PrecompiledHeader" jetzt viermal für verschiedene Konfigurationsoptionen (Release / Debug) und Plattform (Win32 / x64) deklariert, aber jedes Mal ist diese Ankündigung dieselbe. Außerdem werden hier mehrere verschiedene ItemGroups verwendet, während in Wirklichkeit ein Element völlig ausreicht. Als Ergebnis kommen wir zu einer kompakten und verständlichen .vcxproj, die einfach 1) die im Projekt enthaltenen Dateien auflistet, 2) was jede von ihnen ist (plus Einstellungen für bestimmte einzelne Dateien) und 3) einen Link zu den separat gespeicherten Projekteinstellungen enthält.
<?xml version="1.0" encoding="utf-8"?><Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="settings.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{0D35456E-42DA-418B-87D4-55E32B8E1373}</ProjectGuid> </PropertyGroup> <ItemGroup> <ClInclude Include="pch.h" /> <ClCompile Include="pch.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> <ClCompile Include="protobuf_test.cpp" /> <Text Include="test.proto" /> </ItemGroup> </Project>
Wir laden das Projekt im Studio neu, überprüfen die Montage - alles funktioniert.
Aber warum sogar .vcxproj und .props trennen?
Da sich in der Baugruppe nichts geändert hat, scheint es auf den ersten Blick so, als hätten wir die Ahle gegen Seife ausgetauscht, wodurch die Datei, in die wir eigentlich nicht schauen mussten, sinnlos "umgestaltet" wurde. Nehmen wir jedoch für einen Moment an, dass unsere Lösung mehr als ein Projekt umfasst. Wie Sie sehen können, können dann mehrere verschiedene .vcxproj-Dateien aus verschiedenen Projekten
dieselbe .props-Datei mit den Einstellungen verwenden. Wir haben
die in der Lösung verwendeten
Assemblyregeln vom Quellcode getrennt und können jetzt die Assemblyeinstellungen für
alle Projekte desselben Typs an einem Ort ändern. In den allermeisten Fällen ist eine solche Vereinheitlichung der Versammlung eine gute Idee. Wenn Sie beispielsweise einer Lösung ein neues Projekt hinzufügen, übertragen wir in einer Aktion auf diese Weise alle Einstellungen aus vorhandenen Projekten in der Lösung.
Aber was ist, wenn wir für verschiedene Projekte immer noch unterschiedliche Einstellungen benötigen? In diesem Fall können wir einfach mehrere verschiedene .props-Dateien für verschiedene Arten von Projekten erstellen. Da .props-Dateien andere .props-Dateien genauso importieren können, ist es recht einfach und natürlich, eine „Hierarchie“ mehrerer .props-Dateien zu erstellen, von Dateien, die allgemeine Einstellungen für alle Projekte in einer Lösung beschreiben, bis zu hochspezialisierten Versionen, in denen spezielle angegeben werden Regeln für nur ein oder zwei Projekte in einer Lösung. In MSBuild gibt es die Regel, dass die spätere Deklaration die frühere überschreibt, wenn dieselbe Einstellung in der Eingabedatei zweimal deklariert wird (z. B. zuerst in base.props importiert und dann erneut in derivative.props deklariert wird, die am Anfang von base.props importiert wird) . Auf diese Weise können Sie einfach und bequem beliebige Hierarchien von Einstellungen festlegen, indem Sie einfach in jeder .props-Datei alle Einstellungen überschreiben, die für eine bestimmte .props-Datei erforderlich sind, ohne sich Gedanken darüber machen zu müssen, dass sie bereits an einer anderen Stelle angekündigt worden sein könnten. Unter anderem ist es in .props ratsam, die Standardeinstellungen der Studio-Umgebung zu importieren, die für ein C ++ - Projekt folgendermaßen aussehen:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
Ich stelle fest, dass es in der Praxis sehr praktisch ist, eigene .props-Dateien im selben Ordner wie die .sln-Datei abzulegen
Weil Sie damit bequem .props unabhängig vom Speicherort von .vcxproj importieren können <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> ... </Project>
Die Projekteinstellungen besser lesbar machen
Jetzt, da wir uns nicht mehr um jedes Projekt einzeln kümmern müssen, können wir dem Anpassen des Erstellungsprozesses mehr Aufmerksamkeit schenken. Und für den Anfang empfehle ich, den interessantesten Objekten im Dateisystem, die sich auf die Lösung mit .props-Dateien beziehen, vernünftige Namen zu geben. Dazu sollten wir ein PropertyGroup-Tag mit der Bezeichnung UserMacros erstellen:
<PropertyGroup Label="UserMacros"> <RepositoryRoot>$(SolutionDir)\..</RepositoryRoot> <ProjectsDir>$(RepositoryRoot)\projects</ProjectsDir> <ThirdPartyDir>$(RepositoryRoot)\..\ThirdParty</ThirdPartyDir> <ProtoBufRoot>$(ThirdPartyDir)\protobuf\src</ProtoBufRoot> </PropertyGroup>
Dann können wir in den Projekteinstellungen anstelle von Konstrukten der Form ".. \ .. \ .. \ ThirdParty \ protobuf \ src \ protoc.exe" einfach "$ (ProtoBufRoot) \ protoc.exe" schreiben. Zusätzlich zu einer besseren Lesbarkeit macht dies den Code viel mobiler - wir können .vcxproj frei verschieben, ohne befürchten zu müssen, dass seine Einstellungen nicht mehr funktionieren, und wir können Protobuf verschieben (oder aktualisieren), indem wir nur eine Zeile in einer der .props-Dateien ändern.
Wenn mehrere PropertyGroups nacheinander deklariert werden, wird deren Inhalt zusammengeführt. Es werden nur Makros überschrieben, deren Namen mit den zuvor deklarierten übereinstimmen. Auf diese Weise können Sie Deklarationen in angehängten .props-Dateien problemlos ergänzen, ohne befürchten zu müssen, bereits angekündigte Makros zu verlieren.
Wir machen es einfach, Bibliotheken von Drittanbietern zu verbinden
Der übliche Vorgang zum Einfügen von Abhängigkeiten von der Drittanbieter-Bibliothek in Visual Studio sieht häufig folgendermaßen aus:

Der Prozess der entsprechenden Einstellungen umfasst das gleichzeitige Bearbeiten mehrerer Parameter auf verschiedenen Registerkarten der Projekteinstellungen und ist daher ziemlich langweilig. Darüber hinaus muss dies normalerweise mehrmals für jede einzelne Konfiguration im Projekt durchgeführt werden. Aufgrund solcher Manipulationen stellt sich häufig heraus, dass das Projekt in der Release-Assembly, nicht jedoch in der Debug-Assembly zusammengestellt wird. Dies ist also ein unangenehmer und unzuverlässiger Ansatz. Aber wie Sie wahrscheinlich bereits vermutet haben, können dieselben Einstellungen in eine Requisitendatei „gepackt“ werden. Für die ZeroMQ-Bibliothek könnte eine ähnliche Datei beispielsweise folgendermaßen aussehen:
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemDefinitionGroup> <ClCompile> <AdditionalIncludeDirectories>$(ThirdPartyDir)\libzmq\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <PreprocessorDefinitions>ZMQ_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> </ClCompile> <Link> <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">libzmq-v120-mt-sgd-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies Condition="'$(Configuration)|$(Platform)'=='Release|x64'">libzmq-v120-mt-s-4_3_1.lib;Ws2_32.Lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalLibraryDirectories>$(ThirdPartyDir)\libzmq\lib\x64\$(Configuration);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> </Link> </ItemDefinitionGroup> </Project>
Beachten Sie, dass, wenn wir einfach ein Tag vom Typ AdditionalLibraryDirectories in der Requisitendatei definieren, alle früheren Definitionen überschrieben werden. Daher wird eine etwas kompliziertere Konstruktion verwendet, bei der das Tag mit einer Folge von Zeichen endet;% (AdditionalLibraryDirectories), die die Verknüpfung des Tags mit sich selbst bilden. In der Semantik von MSBuild wird dieses Makro auf den vorherigen Tag-Wert erweitert, sodass bei einer ähnlichen Konstruktion
die Parameter an den Anfang der im Parameter AdditionalLibraryDirectories gespeicherten Zeile
angehängt werden.
Um ZeroMQ jetzt zu verbinden, reicht es aus, einfach die angegebene .props-Datei zu importieren.
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> <Import Project="$(SolutionDir)\zeromq.props" /> ... </Project>
Und hier enden die Manipulationen mit dem Projekt - MSBuild schließt automatisch die erforderlichen Header-Dateien und Bibliotheken in Release- und Debug-Assemblys ein. Wenn wir ein wenig Zeit damit verbringen, zeromq.props zu schreiben, haben wir die Möglichkeit, ZeroMQ zuverlässig und genau mit jedem Projekt in nur einer Zeile zu verbinden. Die Entwickler des Studios haben dafür sogar eine spezielle GUI namens Property Manager bereitgestellt, damit Mausliebhaber den gleichen Vorgang mit wenigen Klicks ausführen können.

Richtig, wie die anderen Studio-Tools, fügt diese GUI dem .vcxproj-Code anstelle einer lesbaren einzeiligen Zeile etwas hinzu
ein solcher Code <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup> <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <Import Project="..\..\..\..\projects\BuildSystem\thirdparty_libraries\PropertySheets\zeromq.props" /> </ImportGroup>
Daher ziehe ich es vor, Links zu .vcxproj-Dateien manuell zu Bibliotheken von Drittanbietern hinzuzufügen.
Ähnlich wie zuvor beschrieben, macht es die Arbeit mit ThirdParty-Komponenten über .props-Dateien ebenso einfach, gebrauchte Bibliotheken in Zukunft zu aktualisieren. Es reicht aus, eine einzelne zeromq.props-Datei zu bearbeiten, und die Assembly der gesamten Lösung wechselt synchron zur neuen Version. In unseren Projekten ist das Erstellen des Projekts über diesen Mechanismus beispielsweise mit dem Conan-Abhängigkeitsmanager verknüpft, der die erforderlichen Bibliotheken von Drittanbietern aus dem Abhängigkeitsmanifest sammelt und automatisch die entsprechenden .props-Dateien generiert.
Projektvorlagen - Automatisieren Sie die Erstellung von Projekten
Das manuelle Bearbeiten von .vcxproj-Dateien, die vom Studio erstellt wurden, ist sicherlich ziemlich langweilig (obwohl, wenn Sie die Fähigkeit haben und nicht lange). Daher bietet das Studio die bequeme Möglichkeit, eigene Vorlagen für neue Projekte zu erstellen, mit denen Sie .vcxproj nur einmal manuell konfigurieren und dann mit einem Klick in einem neuen Projekt wiederverwenden können. Im einfachsten Fall müssen Sie nicht einmal etwas manuell bearbeiten. Öffnen Sie einfach das Projekt, das Sie in eine Vorlage verwandeln möchten, und wählen Sie im Menü Projekt \ Vorlage exportieren aus. In dem sich öffnenden Dialogfeld können Sie mehrere einfache Parameter angeben, z. B. den Namen der Vorlage oder Zeile, die in der Beschreibung angezeigt wird, und auswählen, ob die neu erstellte Vorlage sofort zum Dialogfeld Neues Projekt hinzugefügt wird. Die auf diese Weise erstellte Vorlage erstellt eine Kopie des zum Erstellen verwendeten Projekts (einschließlich aller im Projekt enthaltenen Dateien) und ersetzt nur den Projektnamen und die darin enthaltene GUID. In einem relativ großen Prozentsatz der Fälle ist dies mehr als genug.
Mit einer detaillierteren Untersuchung der von Studio generierten Vorlage können Sie leicht sicherstellen, dass es sich nur um ein Zip-Archiv handelt, in dem sich alle in der Vorlage verwendeten Dateien und eine zusätzliche Konfigurationsdatei mit der Erweiterung .vstemplate befinden. Diese Datei enthält eine Liste der Projektmetadaten (wie das verwendete Symbol oder die Beschreibungszeile) und eine Liste der Dateien, die beim Erstellen eines neuen Projekts erstellt werden müssen. Zum Beispiel
<VSTemplate Version="3.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Project"> <TemplateData> <Name>C++ console application</Name> <Description>C++ console application for our project</Description> <ProjectType>VC</ProjectType> <ProjectSubType> </ProjectSubType> <SortOrder>1000</SortOrder> <CreateNewFolder>true</CreateNewFolder> ` <DefaultName>OurCppConsoleApp</DefaultName> <ProvideDefaultName>true</ProvideDefaultName> <LocationField>Enabled</LocationField> <EnableLocationBrowseButton>true</EnableLocationBrowseButton> <Icon>ng.ico</Icon> </TemplateData> <TemplateContent> <Project TargetFileName="$projectname$.vcxproj" File="console_app.vcxproj" ReplaceParameters="true"> <ProjectItem ReplaceParameters="false" TargetFileName="$projectname$.vcxproj.filters">console_app.vcxproj.filters</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="main.cpp">main.cpp</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="stdafx.cpp">stdafx.cpp</ProjectItem> <ProjectItem ReplaceParameters="false" TargetFileName="stdafx.h">stdafx.h</ProjectItem> </Project> </TemplateContent> </VSTemplate>
Beachten Sie den Parameter ReplaceParameters = "true". In diesem Fall gilt dies nur für die Datei vcxproj, die folgendermaßen aussieht:
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(SolutionDir)\console_app.props" /> <PropertyGroup Label="Globals"> <ProjectGuid>{$guid1$}</ProjectGuid> <RootNamespace>$safeprojectname$</RootNamespace> </PropertyGroup> <ItemGroup> <ClCompile Include="main.cpp" /> <ClCompile Include="stdafx.cpp"> <PrecompiledHeader>Create</PrecompiledHeader> </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="stdafx.h" /> </ItemGroup> </Project>
Wie Sie sehen, gibt es anstelle der GUID und des RootNamespace keine spezifischen Werte, sondern die Stubs $ guid1 $ und $ safeprojectname $. Bei Verwendung der Vorlage durchsucht das Studio die mit ReplaceParamters = "true" gekennzeichneten Dateien, sucht in ihnen nach Stubs der Form $ name $ und ersetzt sie durch berechnete Werte mithilfe eines speziellen Wörterbuchs. Standardmäßig unterstützt Studio
nicht sehr viele Parameter . Wenn Sie jedoch Visual Studio-Erweiterungen schreiben (auf die wir später noch eingehen werden), können Sie beim Starten des Dialogfelds zum Erstellen eines neuen Projekts aus der Vorlage so viele eigene Parameter hinzufügen, die berechnet (oder vom Benutzer eingegeben) wurden. Wie Sie in der .vstemplate-Datei sehen können, kann mit demselben Wörterbuch auch ein Dateiname generiert werden, mit dem insbesondere eindeutige Namen für .vcxproj-Dateien für verschiedene Projekte generiert werden können. Wenn Sie ReplaceParameters = false setzen, wird die in der Vorlage angegebene Datei einfach ohne zusätzliche Verarbeitung kopiert.
Das resultierende ZIP-Archiv mit der Vorlage kann auf verschiedene Arten zur Liste der dem Studio bekannten Vorlagen hinzugefügt werden. Am einfachsten kopieren Sie diese Datei einfach in den Ordner
% USERPROFILE% \ Documents \ Visual Studio XX \ Templates \ ProjectTemplates . Es ist anzumerken, dass trotz der Tatsache, dass in diesem Ordner viele verschiedene Unterordner gefunden werden, die mit den Namen der Ordner im Fenster zum Erstellen eines neuen Projekts übereinstimmen, die Vorlage einfach im Stammordner abgelegt werden sollte, da die Position der Vorlage im Baum neuer Projekte vom Studio aus den Tags ProjectType und ProjectSubType bestimmt wird in der .vstemplate-Datei. Diese Methode eignet sich am besten zum Erstellen von "persönlichen" Vorlagen, die nur für Sie eindeutig sind. Wenn Sie im Dialogfeld "Vorlage exportieren" das Kontrollkästchen "Vorlage automatisch in Visual Studio importieren" aktivieren, legt Studio das beim Export erstellte Zip-Archiv genau in diesem Ordner ab Muster. Das Teilen solcher Vorlagen mit Kollegen durch manuelles Kopieren ist jedoch sicherlich nicht sehr praktisch. Machen wir uns also mit einer etwas erweiterten Option vertraut - erstellen Sie eine Visual Studio-Erweiterung (.vsix)
Um VSIX zu erstellen, müssen Sie die optionale Studio-Komponente installieren, die als Visual Studio Extensions-Entwicklungstool bezeichnet wird:

Danach wird die Option "VSIX-Projekt" im Abschnitt "Visual C # \ Extensibility" angezeigt. Bitte beachten Sie, dass es trotz seines Speicherorts (C #) zum Erstellen von Erweiterungen verwendet wird, einschließlich Sätzen von C ++ - Projektvorlagen.

In dem von VSIX erstellten Projekt können Sie viele verschiedene Dinge tun - beispielsweise ein eigenes Dialogfeld erstellen, in dem von der Vorlage erstellte Projekte konfiguriert werden. Dies ist jedoch ein separates großes Diskussionsthema, auf das ich in diesem Artikel nicht eingehen werde. Um Vorlagen in VSIX zu erstellen, ist alles sehr einfach: Erstellen Sie ein leeres VSIX-Projekt, öffnen Sie die .vsixmanifest-Datei und legen Sie alle Daten für das Projekt direkt in der GUI fest. Geben Sie die Metadaten (Nebenstellenname, Beschreibung, Lizenz) auf der Registerkarte Metadaten ein. Beachten Sie das Feld "Version" in der oberen rechten Ecke. Es ist wünschenswert, es korrekt anzugeben, da das Studio es anschließend verwendet, um zu bestimmen, welche Version der Erweiterung auf dem Computer installiert ist. Dann gehen wir zur Registerkarte "Assets" und wählen "Neues Asset hinzufügen" mit Typ: Microsoft.VisualStudio.ProjectTemplate, Quelle: Datei im Dateisystem, Pfad: (Name für das Zip-Archiv mit der Vorlage). Klicken Sie auf OK und wiederholen Sie den Vorgang, bis alle gewünschten Vorlagen zu VSIX hinzugefügt wurden.

Danach müssen Sie noch Konfiguration: Freigeben auswählen und die Build-Lösung befehlen. Sie müssen keinen Code schreiben und Konfigurationsdateien auch manuell bearbeiten. Die Ausgabe ist eine tragbare Datei mit der Erweiterung .vsix, die tatsächlich ein Installationsprogramm für die von uns erstellte Erweiterung ist. Die erstellte Datei wird auf jedem Computer mit installiertem Studio „gestartet“. Zeigen Sie einen Dialog mit der Beschreibung der Erweiterung und der Lizenz an und bieten Sie an, den Inhalt zu installieren. Installation zulassen - Wir erhalten die Hinzufügung unserer Vorlagen im Dialogfeld "Neues Projekt erstellen"

Ein solcher Ansatz macht es einfach, die Arbeit einer großen Anzahl von Personen an einem Projekt zu vereinheitlichen. Für die Installation und Verwendung der Vorlagen benötigt der Benutzer keine Qualifikationen außer ein paar Mausklicks. Die installierte Erweiterung kann im Dialogfeld Extras \ Erweiterungen und Updates angezeigt (und entfernt) werden

Stufe 2: Benutzerdefinierte benutzerdefinierte Kompilierung
OK, zu diesem Zeitpunkt haben wir herausgefunden, wie vcxproj- und Requisitendateien organisiert sind, und gelernt, wie man sie organisiert. Nehmen wir nun an, wir möchten unserem Projekt einige .proto-Schemata zum Serialisieren von Objekten hinzufügen, die auf der wunderbaren Google Protocol Buffers-Bibliothek basieren. Ich möchte Sie an die Hauptidee dieser Bibliothek erinnern: Sie schreiben eine Beschreibung des Objekts („Schema“) in einer speziellen plattformunabhängigen Metasprache (.proto-Datei), die von einem speziellen Compiler (protoc.exe) kompiliert wird, in .cpp / .cs / .py / .java / usw. Dateien, die die Serialisierung / Deserialisierung von Objekten gemäß diesem Schema in der gewünschten Programmiersprache implementieren und die Sie in Ihrem Projekt verwenden können. Daher müssen wir beim Kompilieren des Projekts zunächst protoc aufrufen, um für uns eine Reihe von CPP-Dateien zu erstellen, die wir in Zukunft verwenden werden.
Traditioneller Ansatz
« » protoc pre-build step .proto-. So etwas wie das:

:
«» Visual Studio ( MSBuild) .proto-.
MSBuild targets
MSBuild, build targets, targets. Clean , Compile , Link Deploy. .vcxproj . nix- make «makefile», : .vcxproj XML- makefile.
-- . ? .vcxproj target- - makefile. target- ? «» .vcxproj target- C++ — .
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
«» - C++- «» Build, Clean Rebuild «» . toolset toolset , , Clang.
toolset- toolset . ,
.
target-. target MSBuild
- Liste der Eingaben
- Liste der Ausgänge
- Abhängigkeiten von anderen Zielen (Abhängigkeiten)
- Zieleinstellungen
- Die Reihenfolge der tatsächlichen Schritte, die vom Ziel ausgeführt werden (Aufgaben)
Beispielsweise empfängt das ClCompile-Ziel eine Liste von CPP-Dateien im Projekt als Eingabe und generiert daraus eine Reihe von OBJ-Dateien, indem es den Compiler cl.exe zieht. ClCompile-Zieleinstellungen werden zu Kompilierungsflags, die an cl.exe übergeben werden. Wenn wir die Zeile in die .vcxproj-Datei schreiben <ClCompile Include="protobuf_test.cpp" />
Dann fügen wir die Include-Datei protobuf_tests.cpp zur Liste der Eingaben dieses Ziels hinzu und wenn wir schreiben <ItemDefinitionGroup> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> </ClCompile> </ItemDefinitionGroup>
Dann weisen wir der ClCompile.PrecompiledHeader-Einstellung den Wert "Use" zu. Dieses Ziel wird dann in das / cl-Flag umgewandelt, das an cl.exe an den Compiler übergeben wird.Versuchen wir, ein Ziel zum Erstellen von .proto-Dateien zu erstellen
Das Hinzufügen eines neuen Ziels wird mithilfe des Ziel-Tags implementiert: <Target Name="GenerateProtobuf"> ...steps to take... </Target>
Traditionell werden Ziele in einer Datei mit der Erweiterung .targets abgelegt. Nicht, dass dies unbedingt erforderlich gewesen wäre (sowohl vcxproj als auch Ziel- und Requisitendateien sind äquivalentes XML), aber dies ist ein Standard-Namensschema, und wir werden uns daran halten. Damit Sie im Code der .vcxproj-Datei jetzt so etwas schreiben können <ItemGroup> <ClInclude Include="cpp.h"/> <ProtobufFile Include="my.proto" /> <ItemGroup>
Das von uns erstellte Ziel muss zur Liste AvailableItemName hinzugefügt werden <ItemGroup> <AvailableItemName Include="ProtobufFile"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup>
. MSBuild «task». — - . « », « », « », « -». Exec protoc.exe Message . target- PrepareForBuild. protobuf.targets
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup> <Target Name="GenerateProtobuf" Inputs="%(ProtobufSchema.FullPath)" Outputs=".\generated\%(ProtobufSchema.Filename).pb.cc" AfterTargets="PrepareForBuild"> <Message Importance="High" Text="Compiling schema %(ProtobufSchema.Identity)" /> <Exec Command="$(Protoc) --cpp_out=.\generated %(ProtobufSchema.Identity)" /> </Target> </Project>
Wir haben hier einen eher nicht trivialen Operator "%" ( Stapeloperator ) verwendet, der "für jedes Element aus der Liste" bedeutet, und automatisch Metadaten hinzugefügt . Die Idee hier ist folgende: Wenn wir Code des Formulars schreiben <ItemGroup> <ProtobufSchema Include="test.proto"> <AdditionalData>Test</AdditionalData> </ProtobufSchema> </ItemGroup>
«ProtobufSchema» «test.proto» () AdditionalData «Test». «ProtobufSchema.AdditionalData» «Test». AdditionalData, MSBuild
Identity ( ), Filename ( ) FullPath ( ). % MSBuild — .. .proto .
<Import Project="protobuf.targets" Label="ExtensionTargets"/>
protobuf.props, proto- .vcxproj- ProtobufSchema
<ItemGroup> ... <ProtobufSchema Include="test.proto" /> <ProtobufSchema Include="test2.proto" /> </ItemGroup>
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 ==========
Hurra! ! .proto . .vcxproj.filters
... <ItemGroup> <ProtobufSchema Include="test.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> <ProtobufSchema Include="test2.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> </ItemGroup> ...
— .
. 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
... <Target Name="PrepareToGenerateProtobuf" Inputs="@(ProtobufSchema)" Outputs=".\generated"> <MakeDir Directories=".\generated"/> </Target> <Target Name="GenerateProtobuf" DependsOnTargets="PrepareToGenerateProtobuf" ...
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 . ,
<Target Name="CleanProtobuf" AfterTargets="Clean"> <RemoveDir Directories=".\generated"/> </Target>
— . 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 . Sie können leicht im Zwischenausgabeordner (Projektname) .tlog gefunden werden. Damit U2DCheck korrekt auf Änderungen in den Quelldateien reagiert, müssen wir in eine der gelesenen tlog-Dateien in diesem Ordner schreiben und U2DCheck muss korrekt auf das Entfernen von Ausgabedateien reagieren - Schreiben in einer der tlog-Schreibdateien.Fluchend kehren wir zur entsprechenden Bearbeitung unseres Ziels zurück ... <Exec Command="$(Protoc) --cpp_out=.\generated %(ProtobufSchema.Identity)" /> <WriteLinesToFile File="$(TLogLocation)\protobuf.read.1.tlog" Lines="^%(ProtobufSchema.FullPath)" />
— : .props , up-to-date. write tlog , target .
Visual Studio 2017 update 15.8 MSBuild
GetOutOfDateItems , .target- .tlog .
U2DCheck ProjectCapability
<ItemGroup> <ProjectCapability Include="NoVCDefaultBuildUpToDateCheckProvider" /> </ItemGroup>
In diesem Fall wird das Studio jedoch MSBuild für dieses Projekt und alle von ihm abhängigen Projekte für jeden Build steuern. Ja, U2DCheck wurde aus einem bestimmten Grund hinzugefügt - es funktioniert nicht so schnell, wie ich es gerne hätte.Finalisieren Sie unser benutzerdefiniertes Ziel
, . MSBuild « » , . @(SelectedFiles).
. . .
— . #include- protobuf- , :). ClCompile.
protobuf.targets <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup> <PropertyGroup> <ProtobufOutputFolder>.\generated</ProtobufOutputFolder> </PropertyGroup> <Target Name="ComputeProtobufInput"> <ItemGroup> <ProtobufCompilerData Include="@(ProtobufSchema)"> <OutputCppFile>$(ProtobufOutputFolder)\%(ProtobufSchema.Filename).pb.cc</OutputCppFile> <OutputPythonFile>$(ProtobufOutputFolder)\%(ProtobufSchema.Filename)_pb2.py</OutputPythonFile> <OutputFiles>%(ProtobufCompilerData.OutputCppFile);%(ProtobufCompilerData.OutputPythonFile)</OutputFiles> </ProtobufCompilerData> <ClCompile Include="%(ProtobufCompilerData.OutputCppFile)"> <PrecompiledHeader>NotUsing</PrecompiledHeader> </ClCompile> </ItemGroup> </Target> <Target Name="PrepareToGenerateProtobuf" Condition="'@(ProtobufSchema)'!=''" Inputs="@(ProtobufSchema)" Outputs="$(ProtobufOutputFolder)"> <MakeDir Directories="$(ProtobufOutputFolder)"/> </Target> <Target Name="GenerateProtobuf" DependsOnTargets="PrepareToGenerateProtobuf;ComputeProtobufInput" Inputs="%(ProtobufCompilerData.FullPath)" Outputs="%(ProtobufCompilerData.OutputFiles)" AfterTargets="PrepareForBuild" BeforeTargets="Compile"> <Message Importance="High" Text="Compiling schema %(ProtobufCompilerData.Identity)" /> <Exec Command="$(Protoc) --cpp_out=$(ProtobufOutputFolder) --python_out=$(ProtobufOutputFolder) %(ProtobufCompilerData.Identity)"> <Output ItemName="GeneratedFiles" TaskParameter="Outputs"/> </Exec> <WriteLinesToFile File="$(TLogLocation)\protobuf.read.1.tlog" Lines="^%(ProtobufCompilerData.FullPath)" /> </Target> <Target Name="CleanProtobuf" AfterTargets="Clean"> <RemoveDir Directories="$(ProtobufOutputFolder)"/> </Target> </Project>
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-
<ItemDefinitionGroup> <CustomBuildStep> <Command>$(Protoc) --cpp_out=.\generated\%(FileName).pb.cc %(FullPath)</Command> <Message>Generate protobuf files</Message> <Outputs>.\generated\%(FileName).pb.cc</Outputs> </CustomBuildStep> </ItemDefinitionGroup> <ItemGroup> ... <CustomBuild Include="test.proto"/> <CustomBuild Include="test2.proto"/> ... </ItemGroup>
Microsoft.CppCommon.targets CustomBuildStep , -, . GUI clean tlog- :). , :
- CustomBuildStep
- 1
- step .props ThirdParty , ..
- CustomBuildStep - ,
build target - . thirdparty DLL . « » xcopy Post-Build Targets. ,

Post-build steps build targets. . , ItemType=Copy

apply . .vcxproj- :
<ItemGroup> ... <ProtobufSchema Include="test2.proto" /> <CopyFileToFolders Include="resource.txt"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders> </ItemGroup>
« », tlog-. « » Custom Build Step , — () .
CopyFilesToFolder wildcards.
<CopyFileToFolders Include="$(LibFolder)\*.dll"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders>
CopyFileToFolders — , .props- thirdparty-. , build target
Copy .
<Target Name="_CopyLog4cppDll" Inputs="$(Log4cppDll)" Outputs="$(Log4cppDllTarget)" AfterTargets="PrepareForBuild"> <Message Text="Copying log4cpp.dll..." importance="high"/> <Copy SourceFiles="$(Log4cppDll)" DestinationFiles="$(Log4cppDllTarget)" SkipUnchangedFiles="true" Retries="10" RetryDelayMilliseconds="500" /> </Target>
task- MS DownloadFile, VerifyFileHash, Unzip . Copy Retry, hard-link .
R Copy wildcards .tlog . ,
<Target Name="_PrepareToCopy"> <ItemGroup> <MyFilesToCopy Include="$(LibFolder)\*.dll"/> <MyFilesToCopy> <DestinationFile>$(TargetFolder)\%(MyFilesToCopy.Filename)%(MyFilesToCopy.Extension)</DestinationFile> </MyFilesToCopy> </ItemGroup> </Target> <Target Name="_Copy" Inputs="@(MyFilesToCopy)" Outputs="%(MyFilesToCopy.DestinationFile)" DependsOnTargets="_PrepareToCopy" AfterTargets="PrepareForBuild"> <Message Text="Copying %(MyFilesToCopy.Filename)..." importance="high" /> <Copy SourceFiles="@(MyFilesToCopy)" DestinationFolder="$(TargetFolder)" SkipUnchangedFiles="true" Retries="10" RetryDelayMilliseconds="500" /> <WriteLinesToFile File="$(TLogLocation)\mycopy.read.1.tlog" Lines="^%(MyFilesToCopy.Identity)" /> <WriteLinesToFile File="$(TLogLocation)\mycopy.write.1.tlog" Lines="^%(MyFilesToCopy.Identity);%(MyFilesToCopy.DestinationFile)" /> </Target>
CopyFileToFolders .
Level 3: GUI Visual Studio
make. XML-, , tlog-… - — - . PropertyPageSchema .
.vcxproj Configuration Properties
$(ProtobufOutputFolder) « protobuf.targets» , IDE. XAML- . , , custom_settings.xml
<?xml version="1.0" encoding="utf-8"?> <ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework"> <Rule Name="CustomProperties" PageTemplate="generic" DisplayName="My own properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile"/> </Rule.DataSource> <StringProperty Name="ProtobufOutputFolder" DisplayName="Protobuf Output Directory" Description="Directory where Protobuf generated files are created." Subtype="folder"> </StringProperty> </Rule> </ProjectSchemaDefinitions>
StringProperty «ProtobufOutputFolder» String Subtype=Folder GUI, XML- project file. ProjectFile UserFile — .vcxproj.user ( VCS) . , protobuf.targets PropertyPageSchema
<ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> <PropertyPageSchema Include="custom_settings.xml"/> </ItemGroup>
, , project properties …

Ja! . , , .vcxproj…
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ProtobufOutputFolder>.\generated_custom</ProtobufOutputFolder> </PropertyGroup>
Condition, . DataSource HasConfigurationCondition=«false». 2017 - - . .
configuration<?xml version="1.0" encoding="utf-8"?>
<Rule.DataSource>
/>
</Rule.DataSource>
<StringProperty Name="ProtobufOutputFolder"
DisplayName="Protobuf Output Directory"
Description="Directory where Protobuf generated files are created."
Subtype="folder">
<StringProperty.DataSource>
/>
</StringProperty.DataSource>
/>
. BoolProperty, StringProperty ( «folder» «file»), StringListProperty, IntProperty, EnumProperty DynamicEnumProperty .vcxproj.
. . Bool
<?xml version="1.0" encoding="utf-8"?> <ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework"> <Rule Name="CustomProperties" PageTemplate="generic" DisplayName="My own properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile"/> </Rule.DataSource> <Rule.Categories> <Category Name="General" DisplayName="General"/> </Rule.Categories> <BoolProperty Name="EnableCommonPCH" Category="General" DisplayName="Enable common precompiled headers" Description="Should we use solution-wide precompiled headers instead of project-specific?"> <BoolProperty.DataSource> <DataSource HasConfigurationCondition="false" /> </BoolProperty.DataSource> </BoolProperty> <StringProperty Name="ProtobufOutputFolder" DisplayName="Protobuf Output Directory" Description="Directory where Protobuf generated files are created." Subtype="folder" Category="General"> <StringProperty.DataSource> <DataSource HasConfigurationCondition="false" /> </StringProperty.DataSource> </StringProperty> <StringProperty Name="Dummy" Visible="false" /> </Rule> </ProjectSchemaDefinitions>

, —
<PropertyGroup> <EnableCommonPCH>true</EnableCommonPCH> </PropertyGroup> <PropertyGroup> <ProtobufOutputFolder>.\generated_ustom</ProtobufOutputFolder> </PropertyGroup>
protobuf- .vcxproj . .xml
<ContentType Name="Protobuf" DisplayName="Google Protobuf Schema" ItemType="ProtobufSchema" /> <ItemType Name="ProtobufSchema" DisplayName="Google Protobuf Schema" /> <FileExtension Name="*.proto" ContentType="Protobuf" />
, .proto

«Google Protobuf Schema». «Add new item», .proto- ( \ Add \ Existing item… ) . « » Item type:

« » « ». DataSource ItemType.
<Rule Name="ProtobufProperties" PageTemplate="generic" DisplayName="Protobuf properties"> <Rule.DataSource> <DataSource Persistence="ProjectFile" ItemType="ProtobufSchema" /> </Rule.DataSource> <Rule.Categories> <Category Name="General" DisplayName="General"/> </Rule.Categories> <StringProperty Name="dllexport_decl" DisplayName="dllexport macro" Description="Add dllexport / dllimport statements controlled by #define with this name." Category="General"> </StringProperty> </Rule>

, .vcxproj
<ProtobufSchema Include="test2.proto"> <dllexport_decl Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MYLIB_EXPORT</dllexport_decl> </ProtobufSchema>
.
Level 4: MSBuild
, , : MSBuild. «» , MSBuild «» UsingTask.
MSBuild , DLL- - :
<UsingTask TaskName="CL" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll" />
«» . DLL . UsingTask TaskFactory. TaskFactory « task-» — «-», Task. CodeTaskFactory C# .props-.
, Qt VS Tools <UsingTask TaskName="GetItemHash" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll"> <ParameterGroup> <Item ParameterType="Microsoft.Build.Framework.ITaskItem" Required="true" /> <Keys ParameterType="System.String[]" Required="true" /> <Hash Output="true" ParameterType="System.String" /> </ParameterGroup> <Task> <Using Namespace="System"/> <Using Namespace="System.Text"/> <Using Namespace="System.IO"/> <Using Namespace="System.IO.Compression"/> <Code Type="Fragment" Language="cs"> <![CDATA[ var data = Encoding.UTF8.GetBytes(string.Concat(Keys.OrderBy(x => x) .Select(x => string.Format("[{0}={1}]", 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()); } ]]> </Code> </Task> </UsingTask>
- — use-case .
. MSBuild Visual Studio . - , : .props, .targets .vcxproj MSBuild «»

, :).
msbuild — solution .