Определение выходных данных ссылки проекта в MSBuild без запуска избыточных перестроек
-
22-09-2019 - |
Вопрос
Как часть решения, содержащего множество проектов, у меня есть проект, который ссылается (через <ProjectReference>
три других проекта в решении, плюс некоторые другие).В AfterBuild
, Мне нужно скопировать выходные данные 3 конкретных зависимых проектов в другое место.
Через различные ответы SO и т.д.способ, которым я решил добиться этого, был:
<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, a build.force
файл, хранящийся в obj/Debug
папка ProjectC, которая затем запускает перестройку ProjectC, если я выполняю сборку всего решения, если проект, содержащий это AfterBuild
целевой, тогда как если исключить этот проект из сборки, это [правильно] не вызовет перестроение ProjectC (и критически перестроение всех зависимых элементов ProjectC).В данном случае это может быть специфичная для VS хитрость, которая не возникла бы в контексте командной сборки или другого вызова MSBuild командной строки (но наиболее распространенным использованием будет использование через VS, поэтому мне нужно решить это в любом случае)
Все зависимые проекты (и остальная часть решения в целом) были созданы в интерактивном режиме с помощью VS, и, следовательно, ProjectRefence
s содержат относительные пути и т.д.Я видел упоминание о том, что это может вызвать проблемы, но без полного объяснения того, почему, или когда это будет исправлено, или как это обойти.Другими словами, меня на самом деле не интересуют, например,преобразование ProjectReference
пути к абсолютным путям путем ручного редактирования .csproj.
Хотя вполне возможно, что я делаю что-то глупое, и кто-нибудь немедленно укажет, что это такое (что было бы здорово), будьте уверены, я потратил много времени на изучение /v:diag
выходы и т.д.(хотя я не пытался создать репро с нуля - это в контексте относительно сложной общей сборки)
Решение
Как отмечалось в моем комментарии, вызов GetTargetPath в указанном проекте возвращает только основную выходную сборку этого проекта.Чтобы получить все локальные сборки, на которые ссылаются ссылки, для проекта, на который ссылается ссылка, это немного сложнее.
Добавьте следующее к каждому проекту, на который вы ссылаетесь и для которого вы хотите получить копилокальные данные:
<Target
Name="ComputeCopyLocalAssemblies"
DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
Returns="@(ReferenceCopyLocalPaths)" />
Моя конкретная ситуация заключается в том, что мне нужно было воссоздать структуру папок конвейера для System.Добавить в папку bin моего хост-проекта верхнего уровня.Это довольно грязно, и я не был доволен предложенными MSDN решениями по замене OutputPath - поскольку это приводит к сбоям на нашем сервере сборки и предотвращает создание структуры папок в другом проекте (например, SystemTest)
Итак, наряду с добавлением вышеупомянутого целевого объекта (используя импорт .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>
Мне также нужно было добавить требуемые метаданные PipelineFolder к фактическим ссылкам на проект.Например:
<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
target просто возвращает 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>
Мой текущее обходное решение основано на этом вопросе SO, то есть, у меня есть:
<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>