MSBuild для копирования динамически сгенерированных файлов в рамках зависимости проекта

У меня есть пользовательская задача msbuild, которая генерирует некоторые выходные файлы в выходной каталог ($ (TargetDir)) ProjectA. Текущий код выглядит примерно так:

<MyCustomTask ...>
   <Output TaskParameter="OutputFiles" ItemName="FileWrites"/>
</MyCustomTask>

ProjectB ссылается на ProjectA, но проблема в том, что при создании ProjectB сгенерированные файлы MyCustomTask не копируются в выходной каталог ProjectB.

Как мы можем получить динамически генерируемые дополнительные файлы, которые будут скопированы как часть зависимости проекта от MSBuild?

Ответ 1

Наконец-то мне удалось автоматически выполнить копирование из Project B без необходимости его изменения. IIya не был так далек от решения, но факт заключается в том, что я не могу создать статически, поскольку список файлов, создаваемых из Project A с MyCustomTask, является динамическим. После копания большего количества в Microsoft.Common.targets, я обнаружил, что ProjectB получит список результатов из Project A, вызвав целевой GetCopyToOutputDirectoryItems. Эта цель зависит от AssignTargetPaths, которая сама зависит от свойства списка целей AssignTargetPathsDependsOn.

Итак, чтобы генерировать динамическое содержимое и автоматически копировать этот контент с помощью стандартной зависимости проекта, нам нужно подключить Project A в двух разных местах:

  • В AssignTargetPathsDependsOn, поскольку он косвенно обозначается Project B в проекте A через GetCopyToOutputDirectoryItems. А также он косвенно называется Проектом А при вызове PrepareResource. Здесь мы просто выводим список файлов, которые будут сгенерированы (по проекту A) или будут использованы Project B. AssignTargetPathsDependsOn вызовет настраиваемую задачу MyCustomTaskList, которая отвечает только за вывод списка файлов (но не для их создания)), этот список файлов создаст динамическое "Содержимое" с помощью CopyOutputDirectory.
  • В BuildDependsOn, чтобы фактически сгенерировать контент в Project A. Это вызовет MyCustomTask, который будет генерировать контент.

Все это было задано в ProjectA:

<!-- In Project A -->

<!-- Task to generate the files -->
<UsingTask TaskName="MyCustomTask" AssemblyFile="$(PathToMyCustomTaskAssembly)"/>

<!-- Task to output the list of generated of files - It doesn't generate the file -->
<UsingTask TaskName="MyCustomTaskList" AssemblyFile="$(PathToMyCustomTaskAssembly)"/>

<!-- 1st PART : When Project A is built, It will generate effectively the files -->
<PropertyGroup>
  <BuildDependsOn>
    MyCustomTaskTarget;
    $(BuildDependsOn);
  </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTaskTarget">
  <!-- Call MyCustomTask generate the files files that will be generated by MyCustomTask -->
  <MyCustomTask
      ProjectDirectory="$(ProjectDir)"
      IntermediateDirectory="$(IntermediateOutputPath)"
      Files="@(MyCustomFiles)"
      RootNamespace="$(RootNamespace)"
      >
  </MyCustomTask>
</Target>

<!-- 2nd PART : When Project B is built, It will call GetCopyToOutputDirectoryItems on ProjectA so we need to generate this list when it is called  -->
<!-- For this we need to override AssignTargetPathsDependsOn in order to generate the list of files -->
<!-- as GetCopyToOutputDirectoryItems  ultimately depends on AssignTargetPathsDependsOn -->
<!-- Content need to be generated before AssignTargets, because AssignTargets will prepare all files to be copied later by GetCopyToOutputDirectoryItems -->
<!-- This part is also called from ProjectA when target 'PrepareResources' is called -->
<PropertyGroup>
  <AssignTargetPathsDependsOn>
    $(AssignTargetPathsDependsOn);
    MyCustomTaskListTarget;
  </AssignTargetPathsDependsOn>
</PropertyGroup>

<Target Name="MyCustomTaskListTarget">

  <!-- Call MyCustomTaskList generating the list of files that will be generated by MyCustomTask -->
  <MyCustomTaskList
      ProjectDirectory="$(ProjectDir)"
      IntermediateDirectory="$(IntermediateOutputPath)"
      Files="@(MyCustomFiles)"
      RootNamespace="$(RootNamespace)"
      >
      <Output TaskParameter="ContentFiles" ItemName="MyCustomContent"/>
  </MyCustomTaskList>

  <ItemGroup>
    <!--Generate the lsit of content generated by MyCustomTask -->
    <Content Include="@(MyCustomContent)" KeepMetadata="Link;CopyToOutputDirectory"/>
  </ItemGroup>
</Target>

Этот метод работает с anykind проектов С#, использующих Common.Targets(поэтому он работает с чистым рабочим столом, WinRT XAML App или Windows Phone 8).

Ответ 2

Кажется, что-то вроде этого работает, либо включайте его вручную в ProjectA.csproj(имейте в виду, что VS имеет плохую привычку иногда разрешать групповые символы в абсолютные пути и перезаписывать .csproj) или динамически вводить сама настраиваемая задача. Кроме того, VS кэширует группы элементов в открытом доступе, поэтому может не копировать файлы или выходить из строя, если они были там, но удалены. В этом случае проекты необходимо перезагрузить или перезапустить VS для групп товаров, которые будут переоценены. MSBuild, TFS и т.д. Всегда должны работать.

<ItemGroup>
  <Content Include="$(TargetDir)\*.txt">
    <Link>%(Filename)%(Extension)</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

Ответ 3

Если вы уже делаете это самостоятельно с помощью MSBuild, можете ли вы добавить "Копировать задачу" , чтобы выталкивать файлы вокруг себя?

Ответ 4

Как я понимаю, вы хотите добавить дополнительный шаг, написав только эту строку в ProjectB.msbuild:

<Import Project="ProjectA.msbuild" />

Чтобы достичь этого, вы можете написать что-то вроде ProjectA:

<PropertyGroup>
  <BuildDependsOn>$(BuildDependsOn);MyCustomTask</BuildDependsOn>
</PropertyGroup>

Это добавляет вашу задачу в список зависимостей задачи сборки.

Обратитесь к этому вопросу за дополнительной информацией: StyleCop MS Build magic? Кто вызывает цель StyleCop?

Ответ 5

Для чего бы это ни стоило, если я поместил <None Link="..." /> в цель, я смог обработать вывод, не показывая его в моем проекте на основе SDK. Кроме того, другие проекты, которые ссылаются на этот проект, получают это как часть результатов.

Э.Г.

    <ItemGroup>
        <WebPackBuildOutput Include="..\..\WebPackOutput\dist\**\*" />
    </ItemGroup>

    <Target Name="WebPackOutputContentTarget" BeforeTargets="BeforeBuild">
        <Message Text="Output dynamic content: @(WebPackBuildOutput)" Importance="high"/>
        <ItemGroup>
            <!-- Manually constructing Link metadata, works in classic projects as well -->
            <None Include="@(WebPackBuildOutput)" Link="dist\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
        </ItemGroup>        
    </Target>