Определение выходных данных ссылки проекта в MSBuild без запуска избыточных перестроек

StackOverflow https://stackoverflow.com/questions/2325598

Вопрос

Как часть решения, содержащего множество проектов, у меня есть проект, который ссылается (через <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, и, следовательно, ProjectRefences содержат относительные пути и т.д.Я видел упоминание о том, что это может вызвать проблемы, но без полного объяснения того, почему, или когда это будет исправлено, или как это обойти.Другими словами, меня на самом деле не интересуют, например,преобразование 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>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top