هذا المقال هو دليل لتخصيص التجميع من مشاريع C ++ Visual Studio. جزئيًا ، تم اختزاله من مواد مقالات مبعثرة حول هذا الموضوع ، جزئيًا نتيجة هندسة عكسية لملفات تكوين Studio القياسية. لقد كتبت هذا بشكل أساسي لأن فائدة الوثائق من Microsoft نفسها حول هذا الموضوع تميل إلى الصفر ، وأردت الحصول على مرجع مناسب في متناول اليد يمكن الوصول إليه لاحقًا وإرساله إلى مطورين آخرين. يحتوي Visual Studio على ميزات مريحة وواسعة لإعداد عمل مناسب حقًا مع المشروعات المعقدة ، وأنا أشعر بالضيق لأنني نظرًا للتوثيق المثير للاشمئزاز ، نادرًا ما تستخدم هذه الميزات الآن.
على سبيل المثال ، دعونا نحاول أن نجعل من الممكن إضافة مخطط flatbuffer إلى Studio ، ويقوم Studio تلقائيًا باستدعاء flatc عند الحاجة إليه (ولم يتصل به في حالة عدم وجود تغييرات) ويسمح لك بتعيين الإعدادات مباشرة من خلال File File

جدول المحتويات
* المستوى 1: تسلق داخل ملفات .vcxprojتحدث عن ملفات .propsولكن لماذا منفصلة .vcxproj و .props؟جعل إعدادات المشروع أكثر قابلية للقراءةنجعل من السهل توصيل مكتبات الطرف الثالثقوالب المشروع - أتمتة إنشاء المشاريع* المستوى 2: تجميع مخصص مخصصالنهج التقليديتلبية أهداف MSBuildدعونا نحاول إنشاء هدف لإنشاء ملفات .protoنأتي مثالنا نموذج إلى الذهنU2DCheck وملفات tlogوضع اللمسات الأخيرة على هدفناماذا عن CustomBuildStep؟نسخ الملفات المناسبة* المستوى 3: التكامل مع واجهة المستخدم الرسومية من Visual Studioنحن نسحب الإعدادات من أحشاء .vcxproj في خصائص التكويناشرح Studios حول أنواع الملفات الجديدةربط الإعدادات مع الملفات الفردية* المستوى 4: توسيع وظائف MSBuildملاحظة: تم اختبار جميع الأمثلة الواردة في هذه المقالة في VS 2017. كجزء من فهمي ، يجب أن تعمل في الإصدارات السابقة من الاستوديو بدءًا من VS 2012 على الأقل ، لكن لا يمكنني الوعد بذلك.
المستوى 1: تسلق داخل ملفات .vcxproj
دعنا نلقي نظرة داخل .vcxproj نموذجي تلقائيا إنشاء Visual Studio.
سيبدو شيء مثل هذا<?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>
فوضى غير قابلة للقراءة ، أليس كذلك؟ وهذا لا يزال ملفًا صغيرًا جدًا وتافهًا تقريبًا. دعنا نحاول تحويلها إلى شيء أكثر قابلية للقراءة ومريحة للإدراك.
تحدث عن ملفات .props
للقيام بذلك ، دعونا نلاحظ حقيقة أن الملف الذي التقطناه هو مستند XML عادي ويمكن تقسيمه بشكل منطقي إلى جزأين ، يسرد أولهما إعدادات المشروع ، والثاني يحتوي على الملفات المضمنة فيه. دعونا فصل هذه النصفين جسديا. للقيام بذلك ، نحتاج إلى علامة الاستيراد التي تمت مصادفتها بالفعل في التعليمات البرمجية ، والتي تعد تناظرية لـ # تتضمن ويسمح لك بتضمين ملف واحد في ملف آخر. نقوم بنسخ ملف .vcxproj الخاص بنا إلى ملف آخر ونزيل منه جميع الإعلانات المتعلقة بالملفات المضمنة في المشروع ، ومن ملف .vcxproj ، بدوره ، نزيل كل شيء
ما عدا الإعلانات المتعلقة بالملفات المضمنة بالفعل في المشروع. عادةً ما يسمى الملف الناتج مع إعدادات المشروع ولكن بدون ملفات في Visual Studio "أوراق الخصائص" ويتم حفظها بالملحق .props. بدوره ، في .vcxproj سنزود استيراد المقابلة
الآن .vcxproj يصف فقط الملفات المضمنة في المشروع ويتم قراءته بسهولة أكبر <?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>
يمكن تبسيطه عن طريق إزالة عناصر XML غير الضرورية. على سبيل المثال ، يتم الآن الإعلان عن الخاصية "PrecompiledHeader" 4 مرات لخيارات التكوين المختلفة (إطلاق / تصحيح) والنظام الأساسي (win32 / x64) ، ولكن في كل مرة يكون هذا الإعلان هو نفسه. بالإضافة إلى ذلك ، يتم استخدام العديد من مجموعات ItemGroup المختلفة هنا ، بينما في الواقع هناك عنصر واحد يكفي. نتيجة لذلك ، وصلنا إلى .vcxproj مضغوط ومفهوم يسرد ببساطة 1) الملفات المضمنة في المشروع ، 2) ما كل منها (بالإضافة إلى الإعدادات الخاصة بملفات فردية محددة) و 3) يحتوي على رابط إلى إعدادات المشروع المخزنة بشكل منفصل.
<?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>
نحن إعادة تحميل المشروع في الاستوديو ، والتحقق من التجمع - كل شيء يعمل.
ولكن لماذا منفصلة .vcxproj و .props؟
نظرًا لأنه لم يتغير أي شيء في التجميع ، فقد يبدو للوهلة الأولى أننا غيرنا الخرامه للصابون ، مما أدى إلى "إعادة بيع" لا طائل منه للملف الذي لم نكن بحاجة إلى البحث فيه. ومع ذلك ، دعنا نقول للحظة أن حلنا يتضمن أكثر من مشروع واحد. ثم ، كما ترون ، يمكن أن تستخدم عدة ملفات .vcxproj مختلفة من مشاريع مختلفة
نفس ملف .props مع الإعدادات. لقد فصلنا
قواعد التجميع المستخدمة في الحل عن الكود المصدري ويمكننا الآن تغيير إعدادات التجميع
لجميع المشاريع من نفس النوع في مكان واحد. في الغالبية العظمى من الحالات ، مثل هذا التوحيد للجمعية هو فكرة جيدة. على سبيل المثال ، إضافة مشروع جديد إلى أحد الحلول ، في إجراء واحد ، نقوم بنقل البيانات إليه بطريقة تافهة بهذه الطريقة جميع الإعدادات من المشاريع الحالية في الحل.
ولكن ماذا لو كنا لا نزال بحاجة إلى إعدادات مختلفة لمشاريع مختلفة؟ في هذه الحالة ، يمكننا ببساطة إنشاء عدة ملفات .props مختلفة لأنواع مختلفة من المشاريع. نظرًا لأن ملفات .props يمكنها استيراد ملفات .props أخرى بالطريقة نفسها تمامًا ، فمن السهل جدًا والطبيعي إنشاء "تسلسل هرمي" للعديد من ملفات .props ، من الملفات التي تصف الإعدادات العامة لجميع المشاريع في حل لإصدارات متخصصة للغاية تحدد قواعد لمشروع واحد أو اثنين فقط في الحل. توجد قاعدة في MSBuild وهي أنه إذا تم الإعلان عن نفس الإعداد مرتين في ملف الإدخال (على سبيل المثال ، يتم استيراده أولاً إلى base.props ثم يتم التصريح عنه مرة أخرى في. . يتيح لك ذلك تعيين تسلسل هرمي تعسفي للإعدادات بسهولة ويسر من خلال تجاوز كل ملف .props بكل الإعدادات اللازمة لملف .props معين دون الحاجة إلى القلق بشأن إمكانية الإعلان عنها بالفعل في مكان آخر. من بين أشياء أخرى ، في مكان ما في .props ، من الحكمة استيراد إعدادات بيئة Studio القياسية والتي تبدو لمشروع C ++ كما يلي:
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
لاحظ أنه من الناحية العملية ، من المريح جدًا وضع ملفات .props الخاصة بك في نفس المجلد مثل ملف .sln
لأنه يسمح لك بسهولة استيراد .props بغض النظر عن موقع .vcxproj <?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> ... </Project>
جعل إعدادات المشروع أكثر قابلية للقراءة
الآن بعد أن لم نعد بحاجة إلى الاهتمام بكل مشروع على حدة ، يمكننا أن نولي المزيد من الاهتمام لتخصيص عملية الإنشاء. وبالنسبة للمبتدئين ، أوصي بإعطاء أسماء عاقلة للكائنات الأكثر إثارة للاهتمام في نظام الملفات المتعلقة بالحل باستخدام ملفات .props. للقيام بذلك ، يجب علينا إنشاء علامة PropertyGroup وضع علامة على UserMacros:
<PropertyGroup Label="UserMacros"> <RepositoryRoot>$(SolutionDir)\..</RepositoryRoot> <ProjectsDir>$(RepositoryRoot)\projects</ProjectsDir> <ThirdPartyDir>$(RepositoryRoot)\..\ThirdParty</ThirdPartyDir> <ProtoBufRoot>$(ThirdPartyDir)\protobuf\src</ProtoBufRoot> </PropertyGroup>
ثم ، في إعدادات المشروع ، بدلاً من بنيات النموذج ".. \ .. \ .. \ ThirdParty \ protobuf \ src \ protoc.exe" يمكننا ببساطة كتابة "$ (ProtoBufRoot) \ protoc.exe". بالإضافة إلى مزيد من قابلية القراءة ، فإن هذا يجعل الكود أكثر تنقلاً - يمكننا نقل .vcxproj بحرية دون خوف من أن إعداداته ستنطلق ويمكننا نقل (أو تحديث) Protobuf عن طريق تغيير سطر واحد فقط في أحد ملفات .props.
عندما يتم الإعلان عن عدة مجموعات برمجية متسلسلة ، سيتم دمج محتوياتها - سيتم فقط استبدال وحدات الماكرو التي تتزامن أسماؤها مع الأسماء المعلنة سابقًا. يسمح لك هذا باستكمال التصريحات بسهولة في ملفات .props المرفقة دون الخوف من فقدان وحدات الماكرو التي تم الإعلان عنها مسبقًا.
نجعل من السهل توصيل مكتبات الطرف الثالث
العملية المعتادة لتضمين التبعيات في مكتبة الجهة الخارجية في Visual Studio غالبًا ما تبدو كالتالي:

تتضمن عملية الإعدادات المقابلة تحرير العديد من المعلمات في وقت واحد في علامات تبويب مختلفة من إعدادات المشروع وبالتالي مملة إلى حد ما. بالإضافة إلى ذلك ، يجب أن يتم ذلك عادة عدة مرات لكل تكوين فردي في المشروع ، وغالبًا ما يتضح من نتيجة هذه المعالجات أن المشروع تم تجميعه في مجموعة Release ، ولكن ليس في مجموعة Debug. لذلك هذا هو نهج غير مريح وغير موثوق بها. ولكن كما قد تكون خمنت بالفعل ، يمكن "ضبط" الإعدادات نفسها في ملف الدعائم. على سبيل المثال ، بالنسبة لمكتبة ZeroMQ ، قد يبدو ملف مشابه مثل هذا:
<?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>
لاحظ أننا إذا حددنا ببساطة علامة من النوع ExtraLibraryDirectories في ملف الدعائم ، فستتجاوز جميع التعريفات السابقة. لذلك ، يتم استخدام إنشاء أكثر تعقيدًا قليلاً حيث تنتهي العلامة بتسلسل من الأحرف ؛٪ (ExtraLibraryDirectories) تشكل رابط العلامة إلى نفسها. في دلالات MSBuild ، يتم توسيع هذا الماكرو إلى قيمة العلامة السابقة ، لذلك
إلحاق إنشاء مماثل المعلمات إلى بداية السطر المخزن في المعلمة ExtraLibraryDirectories.
للاتصال بـ ZeroMQ الآن ، يكفي استيراد ملف .props المحدد.
<?xml version="1.0" encoding="utf-8"?> <Project DefaultTargets="Build" ...> <Import Project="$(SolutionDir)\settings.props" /> <Import Project="$(SolutionDir)\zeromq.props" /> ... </Project>
وهذا هو المكان الذي يتم فيه التلاعب بنهاية المشروع - سيقوم MSBuild تلقائيًا بتضمين ملفات الرأس والمكتبات الضرورية في كل من تجميعات الإصدار و Debug. وبالتالي ، إذا أمضينا بعض الوقت في كتابة zeromq.props ، سنحصل على فرصة لربط ZeroMQ بدقة وبدقة بأي مشروع في سطر واحد فقط. قدم منشئو الاستوديو لهذا واجهة مستخدم رسومية خاصة تسمى "مدير الممتلكات" ، بحيث يمكن لعشاق الماوس القيام بنفس العملية بنقرات قليلة.

صحيح ، مثل أدوات Studio الأخرى ، فإن واجهة المستخدم الرسومية هذه ستضيف شيئًا ما إلى رمز .vcxproj بدلاً من سطر مفرد قابل للقراءة
مثل هذا الرمز <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>
لذلك أفضل إضافة روابط إلى مكتبات الجهات الخارجية إلى ملفات .vcxproj يدويًا.
على غرار ما تمت مناقشته مسبقًا ، فإن العمل مع مكونات ThirdParty من خلال ملفات .props يجعل من السهل أيضًا تحديث المكتبات المستخدمة في المستقبل. يكفي تحرير ملف zeromq.props واحد وسوف يتحول تجميع الحل بالكامل بشكل متزامن إلى الإصدار الجديد. على سبيل المثال ، في مشاريعنا ، يرتبط بناء المشروع من خلال هذه الآلية بمدير التبعية Conan ، الذي يجمع المجموعة الضرورية من مكتبات الأطراف الثالثة من بيان التبعية ويقوم تلقائيًا بإنشاء ملفات .props المقابلة.
قوالب المشروع - أتمتة إنشاء المشاريع
من المؤكد أن تحرير ملفات .vcxproj التي أنشأها الاستوديو ممل تمامًا (على الرغم من أن لديك مهارة وليس لفترة طويلة). لذلك ، يوفر Studio فرصة ملائمة لإنشاء قوالب خاصة بك للمشاريع الجديدة التي تتيح لك تكوين .vcxproj يدويًا مرة واحدة فقط ، ثم إعادة استخدامها بنقرة واحدة في أي مشروع جديد. في أبسط الحالات ، لن تحتاج حتى إلى تعديل أي شيء يدويًا - فقط افتح المشروع الذي تحتاج إلى تحويله إلى قالب وتحديد Project \ Export Template من القائمة. في مربع الحوار الذي يفتح ، يمكنك تحديد العديد من المعلمات التافهة ، مثل اسم القالب أو الخط الذي سيتم عرضه في وصفه ، وكذلك تحديد ما إذا كان سيتم إضافة القالب الذي تم إنشاؤه حديثًا إلى مربع الحوار مشروع جديد على الفور. يقوم القالب الذي تم إنشاؤه بهذه الطريقة بإنشاء نسخة من المشروع المستخدم لإنشائه (بما في ذلك جميع الملفات المضمنة في المشروع) ، مع استبدال اسم المشروع فقط ومعرف GUID به. في نسبة كبيرة إلى حد ما من الحالات هذا أكثر من كافية.
من خلال فحص أكثر تفصيلاً للقالب الذي تم إنشاؤه بواسطة Studio ، يمكنك بسهولة التأكد من أنه مجرد أرشيف مضغوط توجد فيه جميع الملفات المستخدمة في القالب وملف تكوين إضافي واحد مع ملحق .vstemplate. يحتوي هذا الملف على قائمة بيانات تعريف المشروع (مثل الأيقونة المستخدمة أو سطر الوصف) وقائمة بالملفات التي يجب إنشاؤها عند إنشاء مشروع جديد. على سبيل المثال
<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>
انتبه للمعلمة ReplaceParameters = "true". في هذه الحالة ، لا ينطبق إلا على ملف vcxproj ، والذي يبدو كما يلي:
<?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>
بدلاً من GUID و RootNamespace ، كما ترون ، لا توجد قيم محددة ، لكن كعب الروتين $ guid1 $ و $ safeprojectname $. عند استخدام القالب ، ينتقل الاستوديو إلى الملفات التي تحمل علامة ReplaceParamters = "true" ، ويبحث عن كعب الروتين للنموذج $ name $ فيها ويستبدلها بالقيم المحسوبة باستخدام قاموس خاص. بشكل افتراضي ،
لا يدعم Studio
العديد من المعلمات ، ولكن عند كتابة إضافات Visual Studio (التي سنتحدث عنها لاحقًا) ، من السهل إضافة العديد من المعلمات الخاصة بك المحسوبة (أو التي أدخلها المستخدم) عند بدء مربع الحوار لإنشاء مشروع جديد من القالب. كما ترى في ملف .vstemplate ، يمكن أيضًا استخدام نفس القاموس لإنشاء اسم ملف ، والذي يسمح ، على وجه الخصوص ، بإنشاء أسماء فريدة لملفات .vcxproj لمشاريع مختلفة. عند إعداد ReplaceParameters = false ، سيتم ببساطة نسخ الملف المحدد في القالب دون معالجة إضافية.
يمكن إضافة أرشيف ZIP الناتج مع القالب إلى قائمة القوالب المعروفة بواسطة Studio بأحد الطرق العديدة. أسهل طريقة هي ببساطة نسخ هذا الملف إلى المجلد
٪ \ Documents \ Visual Studio XX \ Templates \ ProjectTemplates . تجدر الإشارة إلى أنه على الرغم من حقيقة أنه في هذا المجلد ستجد العديد من المجلدات الفرعية المختلفة التي تطابق أسماء المجلدات في النافذة لإنشاء مشروع جديد ، في الواقع يجب وضع القالب ببساطة في المجلد الجذر حيث يتم تحديد موضع القالب في شجرة المشاريع الجديدة بواسطة Studio من نوع ProjectType وعلامات ProjectSubType في ملف .vstemplate. هذه الطريقة مناسبة لإنشاء قوالب "شخصية" فريدة من أجلك فقط ، وإذا حددت مربع الاختيار "استيراد القالب تلقائيًا إلى Visual Studio" في مربع الحوار "تصدير القالب" ، فهذا بالضبط ما سيفعله Studio بوضع أرشيف مضغوط تم إنشاؤه أثناء التصدير في هذا المجلد مع القوالب. ومع ذلك ، فإن مشاركة مثل هذه القوالب مع الزملاء عن طريق نسخها يدويًا ليس بالتأكيد مريحًا للغاية. لذلك دعونا نتعرف على خيار أكثر تقدماً بقليل - إنشاء ملحق Visual Studio (.vsix)
لإنشاء VSIX ، نحتاج إلى تثبيت مكون Studio الاختياري ، والذي يسمى أداة تطوير Visual Studio Extensions:

بعد ذلك ، سيظهر خيار "مشروع VSIX" في قسم Visual C # \ Extensibility. يرجى ملاحظة أنه على الرغم من موقعه (C #) ، فإنه يستخدم لإنشاء أي ملحقات ، بما في ذلك مجموعات من قوالب مشروع C ++.

في المشروع الذي تم إنشاؤه بواسطة VSIX ، يمكنك القيام بالكثير من الأشياء المختلفة - على سبيل المثال ، إنشاء مربع الحوار الخاص بك الذي سيتم استخدامه لتكوين المشاريع التي تم إنشاؤها بواسطة القالب. لكن هذا موضوع ضخم منفصل للمناقشة ، ولن أتطرق إليه في هذا المقال. لإنشاء قوالب في VSIX ، كل شيء بسيط للغاية: إنشاء مشروع VSIX فارغ ، وفتح ملف .vsixmanifest وتعيين جميع البيانات للمشروع مباشرة في واجهة المستخدم الرسومية. أدخل البيانات الوصفية (اسم الامتداد والوصف والترخيص) في علامة التبويب بيانات التعريف. انتبه إلى حقل "الإصدار" الموجود في الركن الأيمن العلوي - من المستحسن تحديده بشكل صحيح ، لأن Studio يستخدمه لاحقًا لتحديد إصدار الامتداد المثبت على الكمبيوتر. ثم نذهب إلى علامة التبويب الأصول وحدد "إضافة أصول جديدة" ، مع النوع: Microsoft.VisualStudio.ProjectTemplate ، المصدر: ملف على نظام الملفات ، المسار: (اسم أرشيف الرمز البريدي مع القالب). انقر فوق "موافق" ، كرر العملية حتى نضيف جميع القوالب المطلوبة إلى VSIX.

بعد ذلك ، يبقى اختيار التكوين: Release وأمر Build Solution. لا تحتاج إلى كتابة رمز ؛ تحرير ملفات التكوين يدويًا أيضًا. الإخراج هو ملف محمول ذو ملحق .vsix ، والذي هو في الواقع مثبّت للملحق الذي أنشأناه. سيتم "تشغيل" الملف الذي تم إنشاؤه على أي جهاز كمبيوتر مثبت عليه Studio ، وإظهار مربع حوار مع وصف الامتداد والترخيص وعرض تثبيت محتوياته. السماح بالتثبيت - نحصل على إضافة قوالبنا في مربع الحوار "إنشاء مشروع جديد"

مثل هذا النهج يجعل من السهل توحيد عمل عدد كبير من الأشخاص في المشروع. لتثبيت القوالب واستخدامها ، لا يحتاج المستخدم إلى أي مؤهلات باستثناء بضع نقرات بالماوس. يمكن عرض الملحق المثبت (وإزالته) في مربع الحوار Tools \ Extensions والتحديثات

المستوى 2: تجميع مخصص مخصص
حسنًا ، في هذه المرحلة اكتشفنا كيف يتم تنظيم ملفات vcxproj و props وتعلمنا كيفية تنظيمها. لنفترض الآن أننا نريد أن نضيف إلى مشروعنا بضعة مخططات .proto لتسلسل الكائنات استنادًا إلى مكتبة Google Protocol Buffers الرائعة. اسمح لي أن أذكرك بالفكرة الرئيسية لهذه المكتبة: أن تكتب وصفًا للكائن ("المخطط") في لغة تعريف خاصة مستقلة عن النظام الأساسي (ملف .proto) يتم تجميعها بواسطة مترجم خاص (protoc.exe) في .cpp / .cs / .py / .java / إلخ الملفات التي تنفذ تسلسل / إلغاء تسلسل الكائنات وفقًا لهذا المخطط بلغة البرمجة المطلوبة والتي يمكنك استخدامها في مشروعك. وبالتالي ، عند تجميع المشروع ، نحتاج أولاً إلى استدعاء protoc والذي سيخلق لنا مجموعة من ملفات .cpp التي سنستخدمها في المستقبل.
النهج التقليدي
تطبيق الجبين الكلاسيكي واضح ومباشر ويتكون ببساطة من إضافة مكالمة بروتوك إلى خطوة ما قبل الإنشاء لمشروع يحتاج إلى ملفات .proto. شيء مثل هذا:
لكن هذه ليست مريحة للغاية:- يجب تحديد قائمة الملفات التي تمت معالجتها بشكل صريح في الأمر
- إذا قمت بتغيير هذه الملفات ، فلن يتم إعادة إنشاء البنية تلقائيًا
- عند تغيير ملفات أخرى في مشروع يتعرف عليه Studio على أنه رموز مصدر ، على العكس من ذلك ، سيتم تنفيذ خطوة ما قبل الإنشاء دون داع
- لا يتم تضمين الملفات التي تم إنشاؤها في تجميع المشروع افتراضيًا
- إذا قمنا بتضمين الملفات التي تم إنشاؤها يدويًا في المشروع ، فسيقوم المشروع بإنشاء خطأ عند فتحه للمرة الأولى (حيث لم يتم إنشاء الملفات بعد بواسطة التجميع الأول).
بدلاً من ذلك ، نحاول "شرح" لبرنامج Visual Studio نفسه (أو بالأحرى ، نظام بناء MSBuild الذي يستخدمه) كيفية التعامل مع ملفات .proto هذه.MSBuild targets
MSBuild, build targets, targets. Clean , Compile , Link Deploy. .vcxproj . nix- make «makefile», : .vcxproj XML- makefile.
ولكن ستوقف وقفة التوقف من قبل قارئ حائر. كيف هذا؟ قبل ذلك ، نظرنا إلى .vcxproj في مشروع بسيط ولم تكن هناك أهداف أو أي أوجه تشابه مع makefile الكلاسيكية. أي نوع من الهدف يمكن مناقشته بعد ذلك؟ اتضح أنها ببساطة "مخفية" في هذا السطر ، والتي تتضمن في .vcxproj مجموعة من الأهداف القياسية لبناء رمز C ++. <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
«» - C++- «» Build, Clean Rebuild «» . toolset toolset , , Clang.
toolset- toolset . ,
.
target-. target MSBuild
- (inputs)
- (outputs)
- targets (dependencies)
- target-
- target- (tasks)
ClCompile .cpp cl.exe .obj . ClCompile cl.exe. .vcxproj
<ClCompile Include="protobuf_test.cpp" />
(Include) protobuf_tests.cpp (inputs) ,
<ItemDefinitionGroup> <ClCompile> <PrecompiledHeader>Use</PrecompiledHeader> </ClCompile> </ItemDefinitionGroup>
«Use» ClCompile.PrecompiledHeader target /Yu cl.exe.
target .proto
target- target:
<Target Name="GenerateProtobuf"> ...steps to take... </Target>
target- .targets. ( vcxproj targets props XML-), . .vcxproj -
<ItemGroup> <ClInclude Include="cpp.h"/> <ProtobufFile Include="my.proto" /> <ItemGroup>
target AvailableItemName
<ItemGroup> <AvailableItemName Include="ProtobufFile"> <Targets>GenerateProtobuf</Targets> </AvailableItemName> </ItemGroup>
سنحتاج أيضًا إلى وصف ما نريده بالضبط مع ملفات الإدخال الخاصة بنا وما الذي يجب أن يحدث في الإخراج. للقيام بذلك ، يستخدم MSBuild كيان يسمى "مهمة". المهمة - هذا هو بعض الإجراءات البسيطة التي يجب القيام بها أثناء تجميع المشروع. على سبيل المثال ، "إنشاء دليل" ، "تجميع ملف" ، "تشغيل أمر" ، "نسخ شيء". في حالتنا ، سوف نستخدم Exec الكامنة لتشغيل protoc.exe والرسالة الكامنة لعرض هذه الخطوة في سجل التحويل البرمجي. نشير أيضًا إلى أن إطلاق هذا الهدف يجب أن يتم مباشرة بعد الهدف القياسي 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>
استخدمنا هنا عامل تشغيل غير تافه "٪" ( مشغل التجميع ) مما يعني "لكل عنصر من القائمة" وقم بإضافة بيانات التعريف تلقائيًا . الفكرة هنا هي: عندما نكتب رمز النموذج <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 ، نعيد كتابة ملفاتنا الأولية في .vcxproj-e على العلامة 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 ==========
الصيحة!
حصل! صحيح ، لم تعد ملفات .proto الخاصة بنا مرئية في المشروع. نحن نتسلق إلى .vcxproj.filters وندخل هناك عن طريق القياس ... <ItemGroup> <ProtobufSchema Include="test.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> <ProtobufSchema Include="test2.proto"> <Filter>Resource Files</Filter> </ProtobufSchema> </ItemGroup> ...
نحن إعادة تحميل المشروع - الملفات مرئية مرة أخرى.نأتي مثالنا نموذج إلى الذهن
ومع ذلك ، في الواقع ، لقد خدعت قليلا. إذا لم تقم بإنشاء المجلد الذي تم إنشاؤه يدويًا قبل بدء الإنشاء ، فسيتعطل البناء فعليًا.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 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 . يمكن العثور عليها بسهولة في مجلد الإخراج المتوسط (project_name) .tlog ولكي يستجيب U2DCheck بشكل صحيح للتغيرات في الملفات المصدر ، نحتاج إلى الكتابة إلى أحد ملفات tlog المقروءة في هذا المجلد ، ولكي يستجيب U2DCheck بشكل صحيح لإزالة ملفات الإخراج - الكتابة في واحدة من ملفات الكتابة tlog.شتم ، نعود إلى التعديل المقابل لهدفنا ... <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>
MSBuild , U2DCheck — .
.target
, . 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 التي تتضمن مكتبات الأطراف الثالثة. ومع ذلك ، إذا كنت ترغب في الحصول على مزيد من التحكم في ما يحدث ، فهناك خيار آخر هو إضافة مهمة 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 نسخ أحرف البدل ولا تقوم بتعبئة ملفات .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 القياسية عادة ما يكون أسهل بكثير.المستوى 3: التكامل مع Visual Studio GUI
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 …

نعم!
ظهرت صفحتنا مع الإعداد لدينا وقراءة الاستوديو القيمة الخاصة به بشكل صحيح. نحن نحاول تغييره ، حفظ المشروع ، راجع .vcxproj ... <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ProtobufOutputFolder>.\generated_custom</ProtobufOutputFolder> </PropertyGroup>
كما ترى من خلال الشرط التقليدي ، ترتبط الإعدادات الافتراضية بتكوين بناء محدد. ولكن إذا رغبت في ذلك ، يمكن حظر ذلك عن طريق تعيين علامة DataSource HasConfigurationCondition = "false". صحيح ، في الاستوديو 2017 يوجد خطأ بسبب عدم عرض إعدادات المشروع إذا لم يكن هناك إعداد واحد على الأقل مرتبط ببعض التكوين بينها. لحسن الحظ ، قد لا يكون هذا الإعداد مرئيًا.الخيار دون ملزمة التكوين<?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 مخصص للتجميع لأسباب واضحة غالباً ما يكون غير مريح. لذلك ، يتم دعم علامة تسمى TaskFactory في العلامة UsingTask. يمكن اعتبار TaskFactory "مترجمًا للمهام" - نقوم بتمريره إلى بعض "meta-code" الأولية ، ويقوم بإنشاء كائن من النوع Task ينفذه. على سبيل المثال ، باستخدام CodeTaskFactory ، يمكنك لصق التعليمات البرمجية المكتوبة في مهام C # مباشرة داخل ملف .props.يتم استخدام نهج مشابه ، على سبيل المثال ، بواسطة أدوات Qt VS <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 .