Определение выходов 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, и, следовательно,ProjectRefences содержат относительные пути и т. д. Я видел упоминание о том, что это может вызвать проблемы, но без полного объяснения почему, или когда это будет исправлено, или как это обойти. В других слова, я действительно не заинтересован, например, в преобразовании 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>