Определение выходов ProjectReference в MSBuild без запуска избыточных перестроений
в рамках решения, содержащего много проектов, у меня есть проект, который ссылается (через <ProjectReference>
три других проекта в решении, плюс некоторые другие). В AfterBuild
, мне нужно скопировать результаты 3 конкретных зависимых проектов в другое место.
через различные ответы SO и т. д. я решил сделать это следующим образом:--13-->
<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" />
однако у меня возникли проблемы с этим. The <MSBuild
шаг IncrementalClean
задача заканчивается удалением ряда выходы ProjectC
. При запуске этого под VS2008, a build.force
файл хранится в obj/Debug
папка ProjectC, которая затем запускает ProjectC перестраивается, если я делаю сборку всего решения, если проект, содержащий это AfterBuild
target, тогда как если исключить этот проект из сборки, он [правильно] не запускает перестроение ProjectC (и тяжело перестроение всех иждивенцев ProjectC). Это может быть против конкретных обман в этом случае не будет происходят в контексте TeamBuild или другого вызова командной строки MSBuild (но наиболее распространенное использование будет через VS, поэтому мне нужно решить это в любом случае)
зависимые проекты (и остальная часть решения в целом) были созданы в интерактивном режиме с VS, и, следовательно,ProjectRefence
s содержат относительные пути и т. д. Я видел упоминание о том, что это может вызвать проблемы, но без полного объяснения почему, или когда это будет исправлено, или как это обойти. В других слова, я действительно не заинтересован, например, в преобразовании ProjectReference
пути к абсолютным путям вручную-редактирование .csproj файл.
хотя вполне возможно, что я делаю что-то глупое, и кто-то сразу укажет, что это (что было бы здорово), будьте уверены, что я потратил много времени, изучая /v:diag
выходы etc. (хотя я не пытался построить репро с нуля - это в контексте относительно сложной сборки)
4 ответов
как отмечено в моем комментарии, вызов GetTargetPath в указанном проекте возвращает только основную выходную сборку этого проекта. Чтобы получить все ссылочные локальные сборки копии ссылочного проекта, это немного запутаннее.
добавьте к каждому проекту, на который вы ссылаетесь, что вы хотите получить CopyLocals:
<Target
Name="ComputeCopyLocalAssemblies"
DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
Returns="@(ReferenceCopyLocalPaths)" />
моя конкретная ситуация заключается в том, что мне нужно было воссоздать структуру папок конвейера для системы.Добавить в папку bin у хозяина проекта. Это довольно грязно, и я не был доволен предлагаемыми MSDN решениями mucking с OutputPath - так как это ломается на нашем сервере сборки и предотвращает создание структуры папок в другом проекте (например, SystemTest)
Так вместе с добавлением вышеуказанной цели (используя a .targets import), я добавил следующее К a .целевой файл, импортируемый каждым "хостом", которому требуется создать папку конвейера:
<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, i.e, у меня есть:
<ItemGroup>
<DependentAssemblies Include="
..\ProjectA\bin$(Configuration)\ProjectA.dll;
..\ProjectB\bin$(Configuration)\ProjectB.dll;
..\ProjectC\bin$(Configuration)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>
это, однако, сломается под TeamBuild (где все выходы заканчиваются в одном каталоге), а также, если имена любого из выходов зависимых проектов изменятся.
EDIT: также ищу любые комментарии о том, есть ли более чистый ответ на то, как сделать hardcoding немного чище, чем:
<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>