Artikel ini adalah panduan untuk menyesuaikan perakitan proyek C ++ Visual Studio. Sebagian, itu berkurang dari bahan artikel yang tersebar tentang topik ini, sebagian adalah hasil rekayasa balik dari file konfigurasi standar Studio. Saya menulisnya terutama karena kegunaan dokumentasi dari Microsoft sendiri pada topik ini cenderung nol, dan saya ingin memiliki referensi yang mudah digunakan yang nantinya dapat diakses dan dikirim ke pengembang lain. Visual Studio memiliki pilihan yang mudah dan luas untuk mengatur pekerjaan yang benar-benar nyaman dengan proyek-proyek yang kompleks, dan saya merasa terganggu melihat bahwa karena dokumentasi yang menjijikkan, fitur-fitur ini sangat jarang digunakan sekarang.
Sebagai contoh, mari kita coba membuat kemungkinan untuk menambahkan skema flatbuffer ke Studio, dan Studio secara otomatis memanggil flatc ketika dibutuhkan (dan tidak memanggilnya ketika tidak ada perubahan) dan memungkinkan Anda untuk mengatur pengaturan langsung melalui Properti File

Daftar isi
* Level 1: naik di dalam file .vcxprojBicara tentang file .propsTetapi mengapa bahkan memisahkan .vcxproj dan .props?Membuat pengaturan proyek lebih mudah dibacaKami membuatnya mudah untuk menghubungkan perpustakaan pihak ketigaProject Templates - mengotomatiskan pembuatan proyek* Level 2: kompilasi kustom khususPendekatan tradisionalMemenuhi target MSBuildMari kita coba membuat target untuk membangun file .protoKami membawa contoh model kami ke pikiranU2DPeriksa dan tlog fileFinalisasi .target kustom kamiBagaimana dengan CustomBuildStep?Menyalin file yang benar* Level 3: mengintegrasikan dengan GUI dari Visual StudioKami mengeluarkan pengaturan dari isi perut .vcxproj di Configuration PropertiesJelaskan Studios tentang tipe file baruKaitkan pengaturan dengan file individual* Level 4: memperluas fungsionalitas MSBuildCATATAN: semua contoh dalam artikel ini diuji di VS 2017. Sebagai bagian dari pemahaman saya, mereka harus bekerja dalam versi studio yang lebih lama dimulai setidaknya dengan VS 2012, tetapi saya tidak bisa menjanjikan ini.
Level 1: naik di dalam file .vcxproj
Mari kita lihat di dalam Visual Studio khas .vcxproj otomatis dihasilkan.
Akan terlihat seperti ini<?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>
Kekacauan yang tidak bisa dibaca, bukan? Dan ini masih file yang sangat kecil dan hampir sepele. Mari kita coba mengubahnya menjadi sesuatu yang lebih mudah dibaca dan nyaman untuk persepsi.
Bicara tentang file .props
Untuk melakukan ini, mari kita perhatikan fakta bahwa file yang telah kita ambil adalah dokumen XML biasa dan dapat secara logis dibagi menjadi dua bagian, yang pertama berisi daftar pengaturan proyek, dan yang kedua berisi file yang termasuk di dalamnya. Mari kita pisahkan dua bagian ini secara fisik. Untuk melakukan ini, kita perlu tag Impor yang sudah ditemukan dalam kode, yang merupakan analog dari #include dan memungkinkan Anda untuk memasukkan satu file ke yang lain. Kami menyalin .vcxproj kami ke beberapa file lain dan menghapus darinya semua deklarasi yang terkait dengan file yang termasuk dalam proyek, dan dari .vcxproj, pada gilirannya, menghapus semuanya
kecuali deklarasi yang berkaitan dengan file yang sebenarnya termasuk dalam proyek. File yang dihasilkan dengan pengaturan proyek tetapi tanpa file di Visual Studio biasanya disebut Lembar Properti dan disimpan dengan ekstensi .props. Pada gilirannya, dalam .vcxproj kami akan menyediakan Impor yang sesuai
Sekarang .vcxproj hanya menjelaskan file yang termasuk dalam proyek dan dibaca jauh lebih mudah <?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>
Itu dapat disederhanakan lebih jauh dengan menghapus elemen XML yang tidak perlu. Misalnya, properti "PrecompiledHeader" sekarang dinyatakan 4 kali untuk opsi konfigurasi yang berbeda (rilis / debug) dan platform (win32 / x64), tetapi setiap kali pengumuman ini sama. Selain itu, beberapa ItemGroup yang berbeda digunakan di sini, sementara pada kenyataannya satu elemen cukup. Sebagai hasilnya, kami datang ke .vcxproj yang ringkas dan dapat dimengerti yang hanya mencantumkan 1) file yang termasuk dalam proyek, 2) apa masing-masingnya (ditambah pengaturan khusus untuk file individual tertentu) dan 3) berisi tautan ke pengaturan proyek yang disimpan secara terpisah.
<?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>
Kami memuat ulang proyek di studio, memeriksa perakitan - semuanya berfungsi.
Tetapi mengapa bahkan memisahkan .vcxproj dan .props?
Karena tidak ada yang berubah dalam majelis, pada pandangan pertama sepertinya kita mengubah penusuk untuk sabun, membuat "refactoring" file yang tidak berguna yang sebenarnya tidak perlu kita lihat. Namun, katakanlah sejenak bahwa solusi kami mencakup lebih dari satu proyek. Kemudian, seperti yang Anda lihat, beberapa file .vcxproj yang berbeda dari proyek yang berbeda dapat menggunakan file .props yang
sama dengan pengaturan. Kami telah memisahkan
aturan rakitan yang digunakan dalam solusi dari kode sumber dan kami sekarang dapat mengubah pengaturan rakitan untuk
semua proyek dari jenis yang sama di satu tempat. Dalam sebagian besar kasus, penyatuan majelis seperti itu adalah ide yang bagus. Misalnya, menambahkan proyek baru ke solusi, dalam satu tindakan kami dengan sepele mentransfernya dengan cara ini semua pengaturan dari proyek yang ada dalam solusi.
Tetapi bagaimana jika kita masih membutuhkan pengaturan yang berbeda untuk proyek yang berbeda? Dalam hal ini, kita cukup membuat beberapa file .props yang berbeda untuk berbagai jenis proyek. Karena file .props dapat mengimpor file .props lainnya dengan cara yang persis sama, cukup mudah dan alami untuk membangun "hierarki" dari beberapa file .props, dari file yang menggambarkan pengaturan umum untuk semua proyek dalam solusi hingga versi yang sangat khusus yang menentukan khusus. aturan hanya untuk satu atau dua proyek dalam solusi. Ada aturan di MSBuild bahwa jika pengaturan yang sama dideklarasikan dua kali dalam file input (misalnya, pertama kali diimpor ke base.props dan kemudian dideklarasikan lagi dalam turunan.props yang diimpor pada awal base.props), maka deklarasi selanjutnya menimpa yang sebelumnya . Ini memungkinkan Anda untuk dengan mudah dan mudah mengatur hierarki pengaturan yang sewenang-wenang dengan hanya mengesampingkan di setiap file .props semua pengaturan yang diperlukan untuk file .props yang diberikan tanpa khawatir tentang fakta bahwa mereka mungkin sudah diumumkan di tempat lain. Antara lain, di suatu tempat di .props, bijaksana untuk mengimpor pengaturan lingkungan Studio standar yang untuk proyek C ++ akan terlihat seperti ini:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
Saya perhatikan bahwa dalam praktiknya sangat mudah untuk menempatkan file .props Anda sendiri di folder yang sama dengan file .sln
Karena ini memungkinkan Anda untuk mengimpor .props dengan mudah terlepas dari lokasi .vcxproj <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> ... </Project>
Membuat pengaturan proyek lebih mudah dibaca
Sekarang kita tidak perlu lagi repot dengan setiap proyek secara individual, kita dapat lebih memperhatikan untuk menyesuaikan proses pembuatan. Dan sebagai permulaan, saya sarankan memberikan nama waras ke objek paling menarik dalam sistem file yang terkait dengan solusi menggunakan file .props. Untuk melakukan ini, kita harus membuat tag PropertyGroup bertanda UserMacros:
<PropertyGroup Label="UserMacros"> <RepositoryRoot>$(SolutionDir)\..</RepositoryRoot> <ProjectsDir>$(RepositoryRoot)\projects</ProjectsDir> <ThirdPartyDir>$(RepositoryRoot)\..\ThirdParty</ThirdPartyDir> <ProtoBufRoot>$(ThirdPartyDir)\protobuf\src</ProtoBufRoot> </PropertyGroup>
Kemudian, dalam pengaturan proyek, alih-alih konstruksi dari bentuk ".. \ .. \ .. \ ThirdParty \ protobuf \ src \ protoc.exe" kita cukup menulis "$ (ProtoBufRoot) \ protoc.exe". Selain keterbacaan yang lebih besar, ini membuat kode ini lebih mobile - kita dapat dengan bebas memindahkan .vcxproj tanpa takut pengaturannya akan hilang dan kita dapat memindahkan (atau memperbarui) Protobuf dengan mengubah hanya satu baris di salah satu file .props.
Ketika beberapa PropertyGroup dideklarasikan secara berurutan, isinya akan digabung - hanya makro yang namanya bertepatan dengan yang sebelumnya dinyatakan akan ditimpa. Ini memungkinkan Anda untuk dengan mudah menambahkan deklarasi dalam file .props terlampir tanpa takut kehilangan makro yang sudah diumumkan sebelumnya.
Kami membuatnya mudah untuk menghubungkan perpustakaan pihak ketiga
Proses yang biasa termasuk dependensi pada perpustakaan pihak ketiga di Visual Studio sering terlihat seperti ini:

Proses pengaturan yang sesuai termasuk mengedit beberapa parameter sekaligus pada tab yang berbeda dari pengaturan proyek dan karena itu agak membosankan. Selain itu, biasanya harus dilakukan beberapa kali untuk setiap konfigurasi individu dalam proyek, sehingga sering sebagai akibat dari manipulasi tersebut ternyata proyek tersebut dirakit dalam rakitan rilis, tetapi tidak dalam rakitan Debug. Jadi ini adalah pendekatan yang tidak nyaman dan tidak dapat diandalkan. Tetapi karena Anda mungkin sudah menebak, pengaturan yang sama dapat "dikemas" ke file props. Misalnya, untuk pustaka ZeroMQ, file serupa mungkin terlihat seperti ini:
<?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>
Perhatikan bahwa jika kita cukup mendefinisikan tag tipe AdditionalLibraryDirectories dalam file props, itu akan menimpa semua definisi sebelumnya. Oleh karena itu, konstruksi yang sedikit lebih rumit digunakan di mana tag berakhir dengan urutan karakter;% (AdditionalLibraryDirectories) yang membentuk tautan tag ke dirinya sendiri. Dalam semantik MSBuild, makro ini diperluas ke nilai tag sebelumnya, jadi konstruksi serupa
menambahkan parameter ke awal baris yang disimpan dalam parameter AdditionalLibraryDirectories.
Untuk menghubungkan ZeroMQ sekarang cukup dengan mengimpor file .props yang diberikan.
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> <Import Project="$(SolutionDir)\zeromq.props" /> ... </Project>
Dan di sinilah manipulasi dengan proyek berakhir - MSBuild akan secara otomatis menyertakan file header dan pustaka yang diperlukan di kedua Rilis dan Debug majelis. Dengan demikian, menghabiskan sedikit waktu menulis zeromq.props, kami mendapat kesempatan untuk secara andal dan akurat menghubungkan ZeroMQ ke proyek apa pun hanya dalam satu baris. Pembuat Studio bahkan menyediakan untuk ini GUI khusus yang disebut Manajer Properti, sehingga pecinta mouse dapat melakukan operasi yang sama dalam beberapa klik.

Benar, seperti alat Studio lainnya, GUI ini akan menambahkan sesuatu seperti ke kode .vcxproj alih-alih satu baris yang dapat dibaca
kode seperti itu <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>
Jadi saya lebih suka menambahkan tautan ke pustaka pihak ketiga ke file .vcxproj secara manual.
Mirip dengan apa yang telah dibahas sebelumnya, bekerja dengan komponen ThirdParty melalui file .props membuatnya sama mudahnya untuk memperbarui perpustakaan yang digunakan di masa depan. Cukup mengedit satu file zeromq.props dan perakitan seluruh solusi akan secara sinkron beralih ke versi baru. Misalnya, dalam proyek kami, membangun proyek melalui mekanisme ini terkait dengan manajer dependensi Conan, yang mengumpulkan set pustaka pihak ketiga yang diperlukan dari manifes dependensi dan secara otomatis menghasilkan file .props yang sesuai.
Project Templates - mengotomatiskan pembuatan proyek
Mengedit file .vcxproj secara manual yang dibuat oleh Studio tentu cukup membosankan (walaupun jika Anda memiliki keterampilan dan tidak lama). Oleh karena itu, Studio menyediakan kesempatan yang nyaman untuk membuat templat Anda sendiri untuk proyek-proyek baru yang memungkinkan Anda untuk mengkonfigurasi secara manual .vcxproj hanya sekali, dan kemudian menggunakannya kembali dengan satu klik dalam proyek baru apa pun. Dalam kasus yang paling sederhana, Anda bahkan tidak perlu mengedit apa pun secara manual - cukup buka proyek yang perlu diubah menjadi templat dan pilih Project \ Ekspor Templat dari menu. Di kotak dialog yang terbuka, Anda dapat menentukan beberapa parameter sepele, seperti nama untuk templat atau garis yang akan ditampilkan dalam uraiannya, serta memilih apakah templat yang baru dibuat akan segera ditambahkan ke kotak dialog Proyek Baru. Template yang dibuat dengan cara ini membuat salinan proyek yang digunakan untuk membuatnya (termasuk semua file yang termasuk dalam proyek), hanya mengganti nama proyek dan GUID-nya di dalamnya. Dalam persentase yang agak besar, ini lebih dari cukup.
Dengan pemeriksaan yang lebih terperinci dari templat yang dihasilkan oleh Studio, Anda dapat dengan mudah memastikan bahwa itu hanya arsip zip tempat semua file yang digunakan dalam templat dan satu file konfigurasi tambahan dengan ekstensi .vstemplate berada. File ini berisi daftar metadata proyek (seperti ikon yang digunakan atau baris deskripsi) dan daftar file yang harus dibuat saat membuat proyek baru. Sebagai contoh
<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>
Perhatikan parameter ReplaceParameters = "true". Dalam hal ini, ini hanya berlaku untuk file vcxproj, yang terlihat seperti ini:
<?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>
Di tempat GUID dan RootNamespace, seperti yang Anda lihat, tidak ada nilai spesifik, tetapi stubs $ guid1 $ dan $ safeprojectname $. Saat menggunakan templat, Studio menelusuri file bertanda ReplaceParamters = "true", mencari stub dari formulir $ name $ di dalamnya dan menggantinya dengan nilai yang dihitung menggunakan kamus khusus. Secara default, Studio
tidak mendukung banyak parameter , tetapi ketika menulis Visual Studio Extensions (yang akan kita bicarakan nanti), mudah untuk menambahkan sebanyak mungkin parameter Anda sendiri yang dihitung (atau dimasukkan oleh pengguna) ketika Anda memulai dialog untuk membuat proyek baru dari template. Seperti yang Anda lihat di file .vstemplate, kamus yang sama juga dapat digunakan untuk menghasilkan nama file, yang memungkinkan, khususnya, untuk menghasilkan nama unik untuk file .vcxproj untuk proyek yang berbeda. Saat mengatur ReplaceParameters = false, file yang ditentukan dalam template hanya akan disalin tanpa pemrosesan tambahan.
Arsip ZIP yang dihasilkan dengan templat dapat ditambahkan ke daftar templat yang dikenal oleh Studio dengan salah satu dari beberapa cara. Cara termudah adalah cukup menyalin file ini ke folder
% USERPROFILE% \ Documents \ Visual Studio XX \ Templates \ ProjectTemplates . Perlu dicatat bahwa terlepas dari kenyataan bahwa dalam folder ini Anda akan menemukan banyak subfolder yang cocok dengan nama folder di jendela untuk membuat proyek baru, pada kenyataannya templat harus diletakkan secara sederhana di folder root karena posisi templat di pohon proyek baru ditentukan oleh Studio dari tag ProjectType dan ProjectSubType dalam file .vstemplate. Metode ini paling cocok untuk membuat templat "pribadi" yang unik hanya untuk Anda, dan jika Anda memilih kotak centang "Impor templat otomatis ke dalam Visual Studio" dalam dialog Templat Ekspor, maka inilah yang akan dilakukan Studio dengan menempatkan arsip zip yang dibuat selama ekspor dalam folder ini dengan pola. Namun, berbagi templat tersebut dengan kolega dengan menyalinnya secara manual tentu tidak nyaman. Jadi mari kita berkenalan dengan opsi yang sedikit lebih maju - buat Visual Studio Extension (.vsix)
Untuk membuat VSIX, kita perlu menginstal komponen Studio opsional, yang disebut alat pengembangan Visual Studio Extensions:

Setelah itu, opsi "VSIX project" akan muncul di bagian Visual C # \ Extensibility. Harap perhatikan bahwa terlepas dari lokasinya (C #), ia digunakan untuk membuat ekstensi apa pun, termasuk set templat proyek C ++.

Dalam proyek yang dibuat oleh VSIX, Anda dapat melakukan banyak hal yang berbeda - misalnya, buat kotak dialog Anda sendiri yang akan digunakan untuk mengonfigurasi proyek yang dibuat oleh templat. Tapi ini adalah topik besar yang terpisah untuk diskusi, yang tidak akan saya bahas dalam artikel ini. Untuk membuat templat di VSIX, semuanya sangat sederhana: buat proyek VSIX kosong, buka file .vsixmanifest dan setel semua data untuk proyek secara langsung di GUI. Masukkan metadata (nama ekstensi, deskripsi, lisensi) pada tab Metadata. Perhatikan bidang "Versi" yang terletak di sudut kanan atas - diinginkan untuk menentukannya dengan benar, karena Studio selanjutnya menggunakannya untuk menentukan versi ekstensi mana yang diinstal pada komputer. Kemudian kita pergi ke tab Aset dan pilih "Tambahkan Aset baru", dengan Ketik: Microsoft.VisualStudio.ProjectTemplate, Sumber: File pada sistem file, Path: (nama untuk arsip zip dengan templat). Klik OK, ulangi prosesnya hingga kami menambahkan semua templat yang diinginkan ke VSIX.

Setelah itu, tetap pilih Configuration: Release dan perintah Build Solution. Anda tidak perlu menulis kode, juga mengedit file konfigurasi secara manual. Outputnya adalah file portabel dengan ekstensi .vsix, yang sebenarnya merupakan penginstal untuk ekstensi yang kami buat. File yang dibuat akan "diluncurkan" di komputer mana pun dengan Studio terinstal, tampilkan dialog dengan deskripsi ekstensi dan lisensi serta penawaran untuk menginstal kontennya. Mengizinkan pemasangan - kami mendapatkan tambahan templat kami di kotak dialog "Buat proyek baru"

Pendekatan semacam itu membuatnya mudah untuk menyatukan pekerjaan sejumlah besar orang dalam suatu proyek. Untuk menginstal dan menggunakan templat, pengguna tidak memerlukan kualifikasi apa pun kecuali beberapa klik mouse. Ekstensi yang diinstal dapat dilihat (dan dihapus) di dialog Tools \ Extensions and Updates

Level 2: kompilasi kustom khusus
OK, pada tahap ini kami menemukan cara mengatur file vcxproj dan props dan belajar bagaimana mengaturnya. Sekarang mari kita asumsikan bahwa kita ingin menambahkan ke proyek kita beberapa skema .proto untuk membuat serialisasi objek berdasarkan pustaka Protokol Google Buffer yang luar biasa. Biarkan saya mengingatkan Anda tentang ide utama dari perpustakaan ini: Anda menulis deskripsi objek ("skema") dalam bahasa meta-bahasa platform-independen (file .proto) khusus yang dikompilasi oleh kompiler khusus (protoc.exe) ke dalam .cpp / .cs / .py / .java / dll. file yang mengimplementasikan serialisasi / deserialisasi objek sesuai dengan skema ini dalam bahasa pemrograman yang diinginkan dan yang dapat Anda gunakan dalam proyek Anda. Jadi, ketika mengkompilasi proyek, pertama-tama kita perlu memanggil protoc yang akan membuat bagi kita satu set file .cpp yang akan kita gunakan di masa depan.
Pendekatan tradisional
Implementasi dahi klasik sangat mudah dan hanya terdiri dari menambahkan panggilan protoc ke langkah pra-bangun untuk proyek yang membutuhkan file .proto. Sesuatu seperti ini:
Tapi ini sangat tidak nyaman:- Diperlukan untuk secara eksplisit menentukan daftar file yang diproses dalam perintah
- Jika Anda mengubah file-file ini, build TIDAK akan dibangun kembali secara otomatis
- Saat mengubah file LAINNYA dalam proyek yang diakui Studio sebagai kode sumber, sebaliknya, langkah pra-bangun akan dilakukan tanpa perlu
- File yang dihasilkan tidak termasuk dalam perakitan proyek secara default
- Jika kita secara manual memasukkan file yang dihasilkan dalam proyek, proyek akan menghasilkan kesalahan ketika kita membukanya pertama kali (karena file belum dihasilkan oleh rakitan pertama).
Sebagai gantinya, kami mencoba untuk "menjelaskan" ke Visual Studio itu sendiri (atau lebih tepatnya, sistem MSBuild build yang digunakannya) bagaimana menangani file .proto tersebut.Memenuhi target MSBuild
Dari sudut pandang MSBuild, perakitan proyek apa pun terdiri dari urutan perakitan entitas yang disebut target bangun, disingkat sasaran. Misalnya, membangun proyek dapat mencakup mengeksekusi target Bersih, yang akan menghapus file sementara yang tersisa dari build sebelumnya, kemudian mengeksekusi target Kompilasi, yang akan mengkompilasi proyek, lalu target Tautan, dan akhirnya target Menyebarkan. Semua target ini bersama dengan aturan untuk perakitan mereka tidak diperbaiki sebelumnya tetapi didefinisikan dalam file .vcxproj itu sendiri. Jika Anda terbiasa dengan make utilitas nix dan kata "makefile" muncul di benak Anda saat itu, maka Anda memang benar: .vcxproj adalah variasi XML pada topik makefile.Tetapi stop-stop-stop akan dikatakan oleh pembaca yang bingung. Bagaimana itu? Sebelum itu, kami melihat .vcxproj dalam proyek sederhana dan tidak ada target atau kesamaan dengan makefile klasik. Target apa yang bisa dibahas kemudian? Ternyata mereka hanya "tersembunyi" di baris ini, yang termasuk dalam .vcxproj seperangkat target standar untuk membangun kode C ++. <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
«» - C++- «» Build, Clean Rebuild «» . toolset toolset , , Clang.
toolset- toolset . ,
.
target-. target MSBuild
- Daftar input
- Daftar output
- Ketergantungan pada target lain (dependensi)
- Pengaturan Target
- Urutan langkah aktual yang dilakukan oleh target (tugas)
Sebagai contoh, target ClCompile menerima daftar file .cpp di proyek sebagai input dan menghasilkan satu set file .obj dari mereka dengan menyeret kompilator cl.exe. Pengaturan target ClCompile berubah menjadi flag kompilasi yang diteruskan ke cl.exe. Ketika kita menulis baris dalam file .vcxproj <ClCompile Include="protobuf_test.cpp" />
kemudian kita tambahkan file Include protobuf_tests.cpp ke daftar input target ini, dan ketika kita menulis <ItemDefinitionGroup> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> </ClCompile> </ItemDefinitionGroup>
kemudian kita menetapkan nilai "Gunakan" ke pengaturan ClCompile.PrecompiledHeader yang targetnya kemudian akan berubah menjadi bendera / Yu yang diteruskan ke cl.exe ke kompiler.Mari kita coba membuat target untuk membangun file .proto
Menambahkan target baru diimplementasikan menggunakan tag target: <Target Name="GenerateProtobuf"> ...steps to take... </Target>
Secara tradisional, target ditempatkan dalam file dengan ekstensi .targets. Bukan berarti itu sangat diperlukan (baik vcxproj dan target dan file props di dalamnya adalah XML yang setara), tetapi ini adalah skema penamaan standar dan kami akan tetap menggunakannya. Sehingga dalam kode file .vcxproj Anda sekarang dapat menulis sesuatu seperti <ItemGroup> <ClInclude Include="cpp.h"/> <ProtobufFile Include="my.proto" /> <ItemGroup>
target yang kita buat harus ditambahkan ke daftar AvailableItemName <ItemGroup> <AvailableItemName Include="ProtobufFile"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup>
Kita juga perlu menjelaskan apa yang ingin kita lakukan dengan file input kita dan apa yang harus terjadi pada output. Untuk melakukan ini, MSBuild menggunakan entitas yang disebut "tugas". Tugas - ini adalah beberapa tindakan sederhana yang perlu dilakukan selama perakitan proyek. Misalnya, "buat direktori", "kompilasi file", "jalankan perintah", "salin sesuatu". Dalam kasus kami, kami akan menggunakan Exec mengintai untuk menjalankan protoc.exe dan Pesan mengintai untuk menampilkan langkah ini dalam log kompilasi. Kami juga mengindikasikan bahwa peluncuran target ini harus dilakukan segera setelah target standar ReadyForBuild. Akibatnya, kami mendapatkan sesuatu seperti file protobuf.targets ini <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>
Kami menggunakan di sini operator yang agak non-sepele "%" ( operator batching ) yang berarti "untuk setiap item dari daftar" dan secara otomatis menambahkan metadata . Idenya di sini adalah ini: ketika kita menulis kode formulir <ItemGroup> <ProtobufSchema Include="test.proto"> <AdditionalData>Test</AdditionalData> </ProtobufSchema> </ItemGroup>
maka catatan ini kita tambahkan ke daftar dengan nama "ProtobufSchema" a child element "test.proto" yang memiliki elemen child (metadata) AdditionalData yang berisi string "Test". Jika kita menulis "ProtobufSchema.AdditionalData" maka kita akan mendapatkan akses ke catatan "Test". Selain metadata AdditionalData yang kami nyatakan secara eksplisit, demi kenyamanan kami, MSBuild yang licik secara otomatis menambah lusinan elemen anak yang sering digunakan yang dijelaskan di sini, di antaranya kami menggunakan Identity (baris sumber), Filename (nama file tanpa ekstensi), dan FullPath ( path lengkap ke file). Catatan dengan tanda% memaksa MSBuild untuk menerapkan operasi yang dijelaskan oleh kami untuk setiap elemen dari daftar - yaitu. ke setiap file .proto secara individual.Tambah sekarang <Import Project="protobuf.targets" Label="ExtensionTargets"/>
di protobuf.props, kami menulis ulang file proto kami di .vcxproj-e pada tag ProtobufSchema <ItemGroup> ... <ProtobufSchema Include="test.proto" /> <ProtobufSchema Include="test2.proto" /> </ItemGroup>
dan periksa perakitan1>------ 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 ==========
Hore!
Menghasilkan! Benar, file .proto kami sekarang tidak lagi terlihat di proyek. Kami naik ke .vcxproj.filters dan masuk ke sana dengan analogi ... <ItemGroup> <ProtobufSchema Include="test.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> <ProtobufSchema Include="test2.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> </ItemGroup> ...
Kami memuat ulang proyek - file terlihat lagi.Kami membawa contoh model kami ke pikiran
Namun, pada kenyataannya, saya sedikit curang. Jika Anda tidak secara manual membuat folder yang dibuat sebelum dimulainya membangun, maka pembangunan itu benar-benar crash.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.
Untuk memperbaikinya, tambahkan target tambahan yang akan membuat folder yang diperlukan ... <Target Name="PrepareToGenerateProtobuf" Inputs="@(ProtobufSchema)" Outputs=".\generated"> <MakeDir Directories=".\generated"/> </Target> <Target Name="GenerateProtobuf" DependsOnTargets="PrepareToGenerateProtobuf" ...
Menggunakan properti DependsOnTarget, kami menunjukkan bahwa sebelum memulai salah satu tugas GenerateProtobuf, Anda harus menjalankan PrepToGenerateProtobuf, dan entri @ (ProtobufSchema) merujuk ke seluruh daftar ProtobufSchema, sebagai entitas tunggal yang digunakan sebagai input untuk tugas ini, sehingga akan diluncurkan hanya sekali.Mulai ulang perakitan - itu berhasil! Mari kita coba sekarang untuk melakukan Rebuild lagi, jadi kali ini pasti untuk memastikan semuanya Em, tapi kemana perginya tugas baru kita? Sedikit debugging - dan kami melihat bahwa tugas-tugas sebenarnya diluncurkan oleh MSBuild, tetapi mereka tidak dieksekusi karena folder yang dihasilkan sudah ada di folder output yang ditentukan. Sederhananya, di Rebuild, Bersihkan untuk. \ File yang dihasilkan tidak berfungsi untuk kami. Perbaiki ini dengan menambahkan target lain1>------ 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 ==========
<Target Name="CleanProtobuf" AfterTargets="Clean"> <RemoveDir Directories=".\generated"/> </Target>
Periksa - itu berfungsi. Clean membersihkan file yang kami buat, Rebuild membuatnya kembali, memanggil Build lagi tidak mulai membangun kembali lagi tanpa perlu. Kami melakukan edit ke salah satu file C ++, mencoba membuat Build lagi .proto-file tidak berubah, jadi protoc tidak restart, semuanya diharapkan. Sekarang kami mencoba mengubah file .proto. Sangat menarik bahwa jika Anda memulai perakitan MSBuild melalui baris perintah secara manual, dan tidak melalui UI dari Studio, maka tidak akan ada masalah seperti itu - MSBuild akan dengan benar mengkompilasi ulang file .pp.cc yang diperlukan. Jika kita mengubah .cpp, maka MSBuild, yang dimulai di studio, akan mengkompilasi ulang tidak hanya itu, tetapi juga file .props yang telah kita ubah sebelumnya . Apa masalahnya?========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
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 ==========
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
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 ==========
U2DPeriksa dan tlog file
Ternyata pencipta Visual Studio merasa bahwa memanggil MSBuild untuk semua orang terlalu mahal dan ... mengimplementasikan "pemeriksaan cepat" mereka sendiri apakah akan membangun proyek atau tidak. Ini disebut U2DCheck dan jika menurut pendapatnya proyek tersebut tidak berubah, maka Studio tidak akan memulai MSBuild untuk proyek ini. Biasanya, U2DCheck bekerja sangat "diam-diam" sehingga beberapa orang menebak keberadaannya, tetapi Anda dapat mengaktifkan bendera yang berguna di registri yang akan memaksa U2DCheck untuk menampilkan laporan yang lebih rinci.Dalam kerjanya, U2DCheck mengandalkan file .tlog khusus. Mereka dapat dengan mudah ditemukan di folder antara-output (project_name) .tlog dan untuk U2DCheck untuk merespons dengan benar terhadap perubahan dalam file sumber, kita perlu menulis ke salah satu file tlog baca di folder ini, dan agar U2DCheck merespons dengan benar penghapusan file output - tulis di salah satu file tulis tlog.Mengutuk, kami kembali ke pengeditan target kami yang sesuai ... <Exec Command="$(Protoc) --cpp_out=.\generated %(ProtobufSchema.Identity)" /> <WriteLinesToFile File="$(TLogLocation)\protobuf.read.1.tlog" Lines="^%(ProtobufSchema.FullPath)" />
Kami memeriksa - itu berfungsi: mengedit file .props memicu pembangunan kembali yang diperlukan, perakitan tanpa adanya pengeditan menunjukkan bahwa proyek ini up-to-date. Dalam contoh ini, untuk kesederhanaan, saya tidak menulis tulis tlog untuk melacak penghapusan file yang dibuat selama kompilasi, tetapi ditambahkan ke target dengan cara yang sama.Dimulai dengan Visual Studio 2017 memperbarui 15.8, tugas GetOutOfDateItems standar baru ditambahkan ke MSBuild yang mengotomatiskan ilmu hitam ini, tetapi karena ini telah terjadi baru-baru ini, hampir semua .target-s kustom terus bekerja dengan file .tlog secara manual.Jika mau, Anda juga dapat menonaktifkan U2DCheck untuk proyek apa pun dengan menambahkan satu baris ke bidang ProjectCapability <ItemGroup> <ProjectCapability Include="NoVCDefaultBuildUpToDateCheckProvider" /> </ItemGroup>
Namun, dalam hal ini, Studio akan mengarahkan MSBuild untuk proyek ini dan semua yang bergantung padanya untuk setiap build, dan ya, U2DCheck ditambahkan karena suatu alasan - itu tidak bekerja secepat yang saya inginkan.Finalisasi .target kustom kami
Hasil yang kami peroleh cukup fungsional, tetapi masih ada ruang untuk perbaikan. Sebagai contoh, di MSBuild ada mode "perakitan selektif" ketika baris perintah menunjukkan bahwa tidak diperlukan untuk merakit seluruh proyek secara keseluruhan, tetapi hanya file individual yang dipilih secara khusus di dalamnya. Dukungan untuk mode ini mensyaratkan bahwa target memeriksa isi daftar @ (SelectedFiles).Selain itu, dalam kode sumber kami, ada cukup banyak baris yang saling mengulang yang harus bersamaan. Sikap yang baik merekomendasikan memberikan semua entitas ini nama yang dapat dibaca dan mengatasinya dengan nama-nama ini. Untuk ini, target khusus yang terpisah sering dibuat yang membuat dan mengisi daftar tambahan dengan semua nama yang akan dibutuhkan di masa depan.Akhirnya, kami masih belum menyadari ide yang dijanjikan di awal - pencantuman otomatis file yang dihasilkan dalam proyek. Kami sudah dapat # menyertakan file header yang dibuat oleh protobuf mengetahui bahwa mereka akan dibuat secara otomatis sebelum kompilasi, tetapi nomor ini tidak berfungsi dengan linker :). Oleh karena itu, kami cukup menambahkan file yang dihasilkan ke daftar ClCompile.Contoh implementasi sisir serupa dari 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>
Pengaturan umum di sini dibuat di PropertyGroup, dan target baru ComputeProtobufInput mengisi daftar file input dan output. Sepanjang jalan (untuk menunjukkan bekerja dengan daftar file output), pembuatan kode dari skema untuk integrasi dengan python ditambahkan. Kami memulai dan memeriksa apakah semuanya berfungsi dengan benar 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 ==========
Bagaimana dengan CustomBuildStep?
Saya harus mengatakan bahwa pengembang Microsoft cukup masuk akal memperkirakan bahwa semua hal di atas, hmm, agak tidak mendua dan kurang didokumentasikan dan mencoba untuk membuat kehidupan programmer lebih mudah dengan memperkenalkan CustomBuildStep kustom. Sebagai bagian dari konsep ini, kita harus mencatat dalam pengaturan file bahwa file .props kita adalah tipe Custom Build Step
Kemudian kita harus menunjukkan langkah-langkah perakitan yang diperlukan di tab Custom Build Step.
Dalam .xxproj ini terlihat seperti ini <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>
Konstruksi ini berfungsi karena fakta bahwa data yang dimasukkan dengan cara ini diganti dalam perut Microsoft.CppCommon.targets menjadi target khusus CustomBuildStep yang, pada umumnya, semua sama seperti yang saya jelaskan di atas. Tapi semuanya berjalan melalui GUI dan tidak perlu berpikir tentang menerapkan clean and tlog-ah :). Jika diinginkan, mekanisme ini dapat digunakan, tetapi saya tidak akan merekomendasikan melakukan ini karena pertimbangan berikut:- CustomBuildStep hanya dapat menjadi satu untuk seluruh proyek
- Dengan demikian, hanya 1 jenis file yang dapat diproses per proyek
- Tidak praktis untuk memasukkan langkah seperti itu dalam file .props yang digunakan untuk menghubungkan perpustakaan ThirdParty, karena perpustakaan yang berbeda dapat saling tumpang tindih
- Jika ada yang rusak di CustomBuildStep, maka memahami apa yang terjadi akan lebih sulit daripada menulis target dari awal.
Menyalin file yang benar
Jenis target bangun yang sangat umum adalah menyalin beberapa file dari satu tempat ke tempat lain. Misalnya, menyalin file sumber daya ke folder dengan proyek yang dikompilasi atau menyalin DLL pihak ketiga ke biner yang dikompilasi. Dan sangat sering operasi ini dilaksanakan "langsung" melalui peluncuran utilitas konsol xcopy di Post-Build Sasaran. Misalnya,
Anda tidak perlu melakukan ini karena alasan yang sama sehingga Anda tidak perlu mencoba mendorong target bangunan lain ke dalam langkah-langkah Pascakonstruksi. Sebagai gantinya, kami dapat langsung memberi tahu Studio bahwa ia perlu menyalin file tertentu. Misalnya, jika file tersebut langsung dimasukkan dalam proyek, maka cukup untuk menentukan ItemType = Salin
Setelah mengklik tombol apply, tab tambahan akan muncul di mana Anda dapat mengonfigurasi di mana dan bagaimana menyalin file yang dipilih. Dalam kode file .vcxproj, akan terlihat seperti ini: <ItemGroup> ... <ProtobufSchema Include="test2.proto" /> <CopyFileToFolders Include="resource.txt"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders> </ItemGroup>
Semuanya akan bekerja di luar kotak, termasuk dukungan yang benar untuk file tlog. Di dalam, itu dilaksanakan sesuai dengan prinsip yang sama "tugas standar khusus untuk menyalin file" sebagai Langkah Membangun Kustom yang saya kritik secara harfiah di bagian sebelumnya, tetapi karena menyalin file adalah operasi yang agak sepele dan kami tidak mendefinisikan ulang operasi itu sendiri (menyalin) tetapi hanya mengubah daftar input dan output file untuknya maka itu berfungsi dengan baik.Saya perhatikan bahwa saat membuat daftar file CopyFilesToFolder, Anda dapat menggunakan wildcard. Sebagai contoh <CopyFileToFolders Include="$(LibFolder)\*.dll"> <DestinationFolders>$(OutDir)</DestinationFolders> </CopyFileToFolders>
Menambahkan file ke daftar CopyFileToFolders mungkin merupakan cara termudah untuk menerapkan penyalinan saat membangun proyek, termasuk dalam file .props yang menyertakan pustaka pihak ketiga. Namun, jika Anda ingin mendapatkan kontrol lebih besar atas apa yang terjadi, maka opsi lain adalah menambahkan tugas Salin khusus ke target build Anda . Sebagai contoh <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>
Sedikit penyimpangantask- MS DownloadFile, VerifyFileHash, Unzip . Copy Retry, hard-link .
Sayangnya, tugas Salin tidak mendukung wildcard dan tidak mengisi file .tlog. Jika diinginkan, ini dapat diterapkan secara manual,misalnya seperti itu <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>
Namun, bekerja dengan CopyFileToFolders standar biasanya akan lebih mudah.Level 3: berintegrasi dengan Visual Studio GUI
Semua yang telah kami lakukan sejauh ini dari sisi mungkin tampak seperti upaya yang agak membosankan untuk mengimplementasikan fungsi make normal pada alat yang tidak terlalu cocok untuk ini. Pengeditan manual file XML, konstruksi tidak jelas untuk menyelesaikan tugas-tugas sederhana, file tutch kruk ... Namun, sistem build Studio memiliki kelebihannya - misalnya, setelah pengaturan awal, ini memberikan rencana bangun yang dihasilkan dengan antarmuka grafis yang baik. Untuk implementasinya, tag PropertyPageSchema digunakan, yang akan kita bicarakan sekarang.Kami mengeluarkan pengaturan dari isi perut .vcxproj di Configuration Properties
Mari kita coba membuatnya sehingga kita dapat mengedit properti $ (ProtobufOutputFolder) dari "implementasi combobuf.targets" yang disisir secara manual di dalam file, tetapi dengan kenyamanan langsung dari IDE. Untuk melakukan ini, kita perlu menulis file XAML khusus dengan deskripsi pengaturan. Buka editor teks dan buat file dengan nama, misalnya, 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>
Selain tag StringProperty itu sendiri, yang menunjukkan kepada Studio keberadaan pengaturan "ProtobufOutputFolder" dengan tipe String dan Subtype = Folder dan menjelaskan bagaimana itu harus ditampilkan dalam GUI, XML-nick ini menunjukkan bahwa informasi ini harus disimpan dalam file proyek. Selain ProjectFile, Anda juga dapat menggunakan UserFile - maka data akan direkam dalam file .vcxproj.user yang terpisah, yang disusun oleh pembuat Studio, ditujukan untuk pengaturan pribadi (tidak disimpan dalam VCS). Kami menghubungkan skema yang dijelaskan oleh kami ke proyek, menambahkan tag PropertyPageSchema ke protobuf.targets kami <ItemGroup> <AvailableItemName Include="ProtobufSchema"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> <PropertyPageSchema Include="custom_settings.xml"/> </ItemGroup>
Agar pengeditan kami mulai berlaku, mulai ulang Studio, muat proyek kami, buka properti proyek dan lihat ...
Ya!
Halaman kami dengan pengaturan kami muncul dan nilai standarnya telah dibaca dengan benar oleh Studio. Kami mencoba mengubahnya, menyimpan proyek, lihat .vcxproj ... <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ProtobufOutputFolder>.\generated_custom</ProtobufOutputFolder> </PropertyGroup>
Seperti yang Anda lihat berdasarkan kondisi Kondisi tradisional, pengaturan default dikaitkan dengan konfigurasi bangunan tertentu. Tetapi jika diinginkan, ini dapat diblokir dengan mengatur flag DataSource HasConfigurationCondition = "false". Benar, di studio 2017 ada bug karena yang pengaturan proyek mungkin tidak ditampilkan jika tidak ada setidaknya satu pengaturan yang terkait dengan beberapa konfigurasi di antara mereka. Untungnya, pengaturan ini mungkin tidak terlihat.Opsi tanpa mengikat ke konfigurasi<?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>
/>
Anda dapat menambahkan sejumlah pengaturan. Jenis yang mungkin termasuk BoolProperty, StringProperty (dengan subtipe opsional "folder" dan "file"), StringListProperty, IntProperty, EnumProperty dan DynamicEnumProperty, yang terakhir diisi dengan cepat dari daftar apa saja yang tersedia di .vcxproj. Baca lebih lanjut tentang ini di sini . Anda juga dapat mengelompokkan pengaturan menjadi beberapa bagian. Misalnya, mari kita coba menambahkan satu pengaturan lagi seperti BoolKode <?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>
Kami me-restart Studio.
Edit pengaturan, simpan proyek - semuanya berfungsi seperti yang diharapkan. <PropertyGroup> <EnableCommonPCH>true</EnableCommonPCH> </PropertyGroup> <PropertyGroup> <ProtobufOutputFolder>.\generated_ustom</ProtobufOutputFolder> </PropertyGroup>
Jelaskan Studios tentang tipe file baru
Sampai sekarang, untuk menambahkan file protobuf ke proyek, kami harus mendaftar secara manual apa yang ada di .vcxproj. Ini dapat dengan mudah diperbaiki dengan menambahkan tiga tag ke .xml yang disebutkan di atas. <ContentType Name="Protobuf" DisplayName="Google Protobuf Schema" ItemType="ProtobufSchema" /> <ItemType Name="ProtobufSchema" DisplayName="Google Protobuf Schema" /> <FileExtension Name="*.proto" ContentType="Protobuf" />
Kami me-restart studio, melihat properti dari file .proto kami.
Sangat mudah untuk melihat file yang sekarang dikenali dengan benar sebagai "Google Protobuf Schema". Sayangnya, item yang sesuai tidak secara otomatis ditambahkan ke dialog "Tambah item baru", tetapi jika kita menambahkan file .proto yang ada (menu konteks proyek \ Tambah \ Item yang ada ...) ke proyek, item tersebut akan dikenali dan ditambahkan dengan benar. Selain itu, "tipe file" baru kami dapat dipilih dalam daftar drop-down Jenis barang:
Kaitkan pengaturan dengan file individual
Selain pengaturan "untuk proyek secara keseluruhan", dengan cara yang sangat mirip, Anda dapat membuat "pengaturan untuk file terpisah. Cukup menentukan atribut ItemType di tag DataSource. <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>
Periksa
Simpan, lihat isi .vcxproj <ProtobufSchema Include="test2.proto"> <dllexport_decl Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">MYLIB_EXPORT</dllexport_decl> </ProtobufSchema>
Semuanya berfungsi seperti yang diharapkan.Level 4: memperluas fungsionalitas MSBuild
Saya tidak pernah memiliki kebutuhan untuk masuk ke dalam proses perakitan begitu dalam, tetapi karena artikel itu ternyata agak besar, saya akan secara singkat menyebutkan kesempatan terakhir untuk penyesuaian: perpanjangan dari MSBuild sendiri. Selain kumpulan tugas "standar" yang agak luas, di MSBuild, tugas dapat "diimpor" dari berbagai sumber menggunakan tag UsingTask. Sebagai contoh, kita dapat menulis ekstensi untuk MSBuild , mengkompilasinya ke pustaka DLL dan mengimpor sesuatu seperti ini: <UsingTask TaskName="CL" AssemblyFile="$(MSBuildThisFileDirectory)Microsoft.Build.CppTasks.Common.dll" />
Ini adalah bagaimana sebagian besar tugas "standar" yang disediakan oleh Studio diimplementasikan. Tetapi untuk membawa-bawa DLL khusus untuk perakitan karena alasan yang jelas seringkali tidak nyaman. Oleh karena itu, tag yang disebut TaskFactory didukung dalam tag UsingTask. TaskFactory dapat dianggap sebagai "kompiler untuk tugas" - kami memberikannya beberapa input "meta-code" ke inputnya, dan itu menghasilkan objek yang mengimplementasikan jenis Tugas dari itu. Misalnya, menggunakan CodeTaskFactory, Anda dapat menempel kode yang ditulis dalam tugas C # tepat di dalam file .props.Pendekatan serupa digunakan, misalnya, oleh 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>
Jika seseorang menggunakan fungsionalitas serupa - tulis tentang use case yang menarik di komentar.Itu saja. Saya harap saya bisa menunjukkan bagaimana, ketika mengatur MSBuild, bekerja dengan proyek besar di Visual Studio dapat dibuat sederhana dan nyaman. Jika Anda berencana menerapkan salah satu di atas, saya akan memberikan sedikit saran: untuk debugging .props, .target, dan .vcxproj, lebih mudah untuk mengatur MSBuild ke level "debugging" logging dimana ia dengan sangat hati-hati menjelaskan tindakannya dengan file input dan output
Terima kasih kepada semua orang yang membaca sampai akhir, saya harap itu ternyata menarik :).Bagikan resep Anda untuk msbuild di komentar - Saya akan mencoba memperbarui pos sehingga berfungsi sebagai panduan lengkap untuk mengonfigurasi solusi di Studio.