تحديد مخرجات المشروع في MSBUILD دون أن تؤدي إلى إعادة بناء زائدة عن الحاجة
-
22-09-2019 - |
سؤال
كجزء من حل يحتوي على العديد من المشاريع ، لدي مشروع يشير (عبر أ <ProjectReference>
ثلاثة مشاريع أخرى في الحل ، بالإضافة إلى البعض الآخر). في ال AfterBuild
, ، أحتاج إلى نسخ مخرجات 3 مشاريع تابعة محددة إلى موقع آخر.
عبر مختلف الإجابات ، إلخ. بالطريقة التي استقرت بها لإنجاز ذلك هي:
<MSBuild
Projects="@(ProjectReference)"
Targets="Build"
BuildInParallel="true"
Condition="'%(Name)'=='ProjectA' OR '%(Name)'=='ProjectB' OR '%(Name)'=='ProjectC'">
<Output TaskParameter="TargetOutputs" ItemName="DependentAssemblies" />
</MSBuild>
<Copy SourceFiles="@(DependentAssemblies)" DestinationFolder="XX" SkipUnchangedFiles="true" />
ومع ذلك ، واجهت مشاكل مع هذا. ال <MSBuild
خطوات IncrementalClean
المهمة تنتهي حذف عدد من مخرجات ProjectC
. عند تشغيل هذا تحت VS2008 ، أ build.force
يتم إيداع الملف في obj/Debug
مجلد ProjectC الذي يؤدي بعد ذلك إلى إعادة بناء ProjectC إذا قمت ببناء على الحل بأكمله إذا كان المشروع يحتوي على هذا AfterBuild
الهدف ، في حين أن المرء يستبعد هذا المشروع من البناء ، فإنه [بشكل صحيح] لا يؤدي إلى إعادة بناء ProjectC (و حاسم إعادة بناء لجميع المعالين من ProjectC). قد يكون هذا خداعًا خاصًا بـ VS في هذه الحالة والذي لن يحدث في سياق بناء TeamBuild أو غيره من سطر القيادة MSBuild (ولكن الاستخدام الأكثر شيوعًا سيكون عبر VS ، لذلك أحتاج إلى حل هذا الاتجاه)
تم إنشاء جميع المشاريع التابعة (وبقية الحل بشكل عام) بشكل تفاعلي مع VS ، وبالتالي ProjectRefence
تحتوي S على مسارات نسبية وما إلى ذلك. لقد رأيت ذكرًا على أنه من المحتمل أن يتسبب في مشاكل - ولكن بدون تفسير كامل للسبب ، أو متى سيتم إصلاحه أو كيفية العمل حوله. بمعنى آخر ، لست مهتمًا حقًا بتحويل ProjectReference
مسارات إلى المسارات المطلقة عن طريق تحرير يدوي.
على الرغم من أنه من الممكن تمامًا ، إلا أنني أفعل شيئًا غبيًا وسيشير شخص ما على الفور إلى ما هو (الذي سيكون رائعًا) /v:diag
المخرجات وما إلى ذلك (على الرغم من أنني لم أحاول بناء ريبو من الألف إلى الياء - وهذا في سياق بناء شامل معقد نسبيا)
المحلول
كما هو مذكور في تعليقي ، فإن استدعاء GetTargetPath على المشروع المشار إليه يعيد فقط مجموعة الإخراج الأساسية لهذا المشروع. للحصول على جميع التجميعات المحلية المرجعية للمشروع المشار إليها ، فهي أكثر فوضى.
أضف ما يلي إلى كل مشروع تشير إلى أنك تريد الحصول على copylocals من:
<Target
Name="ComputeCopyLocalAssemblies"
DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
Returns="@(ReferenceCopyLocalPaths)" />
وضعي الخاص هو أنني بحاجة إلى إعادة إنشاء هيكل مجلد خط الأنابيب للنظام. هذا أمر فوضوي نوعًا ما ولم أكن راضيا عن حلول MSDN المقترحة من mucking مع OutputPath - حيث أن ذلك ينفصل عن خادم البناء الخاص بنا ويمنع إنشاء بنية المجلد في مشروع مختلف (مثل اختبار النظام)
لذلك إلى جانب إضافة الهدف أعلاه (باستخدام استيراد .Targets) ، أضفت ما يلي إلى ملف .Targets الذي يستورده كل "مضيف" يحتاج إلى مجلد خط الأنابيب الذي تم إنشاؤه:
<Target
Name="ComputePipelineAssemblies"
BeforeTargets="_CopyFilesMarkedCopyLocal"
Outputs="%(ProjectReference.Identity)">
<ItemGroup>
<_PrimaryAssembly Remove="@(_PrimaryAssembly)" />
<_DependentAssemblies Remove="@(_DependentAssemblies)" />
</ItemGroup>
<!--The Primary Output of the Pipeline project-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="GetTargetPath"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_PrimaryAssembly" />
</MSBuild>
<!--Output of any Referenced Projects-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="ComputeCopyLocalAssemblies"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_DependentAssemblies" />
</MSBuild>
<ItemGroup>
<ReferenceCopyLocalPaths Include="@(_PrimaryAssembly)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
<ReferenceCopyLocalPaths Include="@(_DependentAssemblies)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
</ItemGroup>
</Target>
كنت بحاجة أيضًا إلى إضافة بيانات التعريف المطلوبة لخط الأنابيب إلى مراجع المشروع الفعلية. علي سبيل المثال:
<ProjectReference Include="..\Dogs.Pipeline.AddInSideAdapter\Dogs.Pipeline.AddInSideAdapter.csproj">
<Project>{FFCD0BFC-5A7B-4E13-9E1B-8D01E86975EA}</Project>
<Name>Dogs.Pipeline.AddInSideAdapter</Name>
<Private>False</Private>
<PipelineFolder>Pipeline\AddInSideAdapter\</PipelineFolder>
</ProjectReference>
نصائح أخرى
يجب أن يعمل الحل الأصلي الخاص بك ببساطة عن طريق التغيير
Targets="Build"
ل
Targets="GetTargetPath"
ال GetTargetPath
الهدف ببساطة يعيد TargetPath
الممتلكات ولا تتطلب البناء.
يمكنك حماية ملفاتك في ProjectC إذا اتصلت بهدف مثل هذا أولاً:
<Target Name="ProtectFiles">
<ReadLinesFromFile File="obj\ProjectC.csproj.FileListAbsolute.txt">
<Output TaskParameter="Lines" ItemName="_FileList"/>
</ReadLinesFromFile>
<CreateItem Include="@(_DllFileList)" Exclude="File1.sample; File2.sample">
<Output TaskParameter="Include" ItemName="_FileListWitoutProtectedFiles"/>
</CreateItem>
<WriteLinesToFile
File="obj\ProjectC.csproj.FileListAbsolute.txt"
Lines="@(_FileListWitoutProtectedFiles)"
Overwrite="true"/>
</Target>
لي يعتمد الحل البديل الحالي على هذا السؤال, ، أي لدي:
<ItemGroup>
<DependentAssemblies Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>
ومع ذلك ، فإن هذا سوف ينهار تحت بناء TeamBuild (حيث تنتهي جميع المخرجات في دليل واحد) ، وأيضًا إذا تغيرت أسماء أي من مخرجات المشاريع التابعة.
تحرير: أيضًا البحث عن أي تعليقات حول ما إذا كان هناك إجابة أنظف لكيفية جعل الترميز المتشددين أكثر نظافة من:
<PropertyGroup>
<_TeamBuildingToSingleOutDir Condition="'$(TeamBuildOutDir)'!='' AND '$(CustomizableOutDir)'!='true'">true</_TeamBuildingToSingleOutDir>
</PropertyGroup>
و:
<ItemGroup>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'!='true'"
Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'=='true'"
Include="
$(OutDir)\ProjectA.dll;
$(OutDir)\ProjectB.dll;
$(OutDir)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>