Определение выходных данных ProjectReference в MSBuild без запуска избыточных перестроений - PullRequest
7 голосов
/ 24 февраля 2010

Как часть решения, содержащего много проектов, у меня есть проект, который ссылается (через <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 файл build.force помещается в папку obj/Debug ProjectC, что затем вызывает перестроение ProjectC, если я выполняю Build для всего решения, если проект содержит эту цель AfterBuild, тогда как если один исключает этот проект из сборки, он [правильно] не вызывает перестроение ProjectC (и критически перестроение всех зависимых от ProjectC). В этом случае это может быть хитрость, специфичная для VS, которая не возникает в контексте вызова TeamBuild или другого вызова MSBuild из командной строки (но наиболее распространенное использование будет через VS, поэтому мне нужно разрешить это в любом случае)

Зависимые проекты (и остальная часть решения в целом) все были созданы в интерактивном режиме с VS, и, следовательно, ProjectRefence содержат относительные пути и т. Д. Я видел упоминание о том, что это может вызвать проблемы - но без полного объяснения, почему, или когда это будет исправлено, или как обойти это. Другими словами, мне не очень интересно, например, преобразование путей ProjectReference в абсолютные путем ручного редактирования .csproj.

Хотя вполне возможно, что я делаю что-то глупое, и кто-то сразу же укажет, что это такое (что было бы здорово), будьте уверены, я потратил много времени, изучая /v:diag выходы и т. Д. (Хотя я не знаю попытался создать репродукцию с нуля - это в контексте относительно сложной общей сборки)

Ответы [ 4 ]

7 голосов
/ 08 января 2014

Как отмечалось в моем комментарии, вызов GetTargetPath для указанного проекта возвращает только первичную выходную сборку этого проекта. Чтобы получить все ссылочные копии локальных сборок ссылочного проекта, нужно немного запутаться.

Добавьте следующее к каждому проекту, на который вы ссылаетесь, для которого хотите получить CopyLocals:

    <Target
    Name="ComputeCopyLocalAssemblies"
    DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
    Returns="@(ReferenceCopyLocalPaths)" /> 

Моя конкретная ситуация заключается в том, что мне нужно было воссоздать структуру папок Pipeline для System.AddIn в папке bin моего проекта Host верхнего уровня. Это немного запутанно, и я не был доволен 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>
6 голосов
/ 27 сентября 2010

Ваше оригинальное решение должно работать, просто изменив

Targets="Build"

до

Targets="GetTargetPath"

Цель GetTargetPath просто возвращает свойство TargetPath и не требует построения.

2 голосов
/ 16 сентября 2010

Вы можете защитить свои файлы в 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>
1 голос
/ 24 февраля 2010

Мой текущий обходной путь основан на этом вопросе 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...