Элемент MSBuild нельзя использовать в задаче MSBuild, ошибка MSB4012

У меня есть следующий файл проекта MSBuild:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Deploy" ToolsVersion="4.0">
  <ItemGroup>
    <Base Include="$(MSBuildProjectDirectory)\.." />
  </ItemGroup>

  <PropertyGroup>
    <BaseDirectory>@(Base->'%(FullPath)')</BaseDirectory>
    <DeployDirectory>$(BaseDirectory)\Deploy</DeployDirectory>
    <Configuration>Release</Configuration>
  </PropertyGroup>

  <Target Name="Deploy" DependsOnTargets="Hello;Clean;Build" />

  <Target Name="Hello">
    <Message Text="Hello world. BaseDirectory=$(BaseDirectory), DeployDirectory=$(DeployDirectory)" />
  </Target>

  <Target Name="Clean">
    <RemoveDir Directories="$(DeployDirectory)" />
  </Target>

  <Target Name="Build">
    <MSBuild Projects="$(BaseDirectory)\DebugConsoleApp\DebugConsoleApp.csproj" Properties="Configuration=$(Configuration);OutputPath=$(DeployDirectory)" ContinueOnError="false" />
  </Target>

</Project>

И когда я запускаю его, я получаю сообщение об ошибке:

C: \ Repositories \ Project \ Build \ Build.proj (22,16): ошибка MSB4012: выражение "@(Base->'%(FullPath)')\Deploy" нельзя использовать в этом контексте. Списки элементов не могут быть объединены с другими строками, в которых ожидается список элементов. Используйте точку с запятой для разделения нескольких списков элементов.

Почему я получаю эту ошибку и как ее избежать? Я использую пункт Base в ItemGroup потому что мне нужно избавиться от .. в пути, и Items разрешить сделать это через %FullPath метаданные. Если я использую только PropertyGroup тогда все отлично работает, но у меня есть это .. на всех путях.

2 ответа

Решение

Трудно точно сказать, что происходит под капотом. Я не нахожусь в MSBuild, поэтому я только слабо знаком с фактической реализацией. Нам понадобится разработчик MSBuild, чтобы ответить на 100% правильный ответ. Но вот что я предполагаю, что происходит (читай: остальная часть этого содержит предположения с моей стороны).

Внутри вас цель, когда вы используете утверждение

Projects="$(BaseDirectory)\DebugConsoleApp\DebugConsoleApp.csproj"

MSBuild замечает, что у вас используется расширение свойства $(BaseDirectory), и что тип параметра для проектов в MSBuild - это массив. Также MSBuild замечает, что BaseDirectory является свойством, которое содержит элемент. Эти свойства не ведут себя как обычные свойства. Вы можете думать о них как о "виртуальных свойствах" (да, я только что придумал этот термин). Когда эти свойства используются вместо поиска значения, происходит внутренняя замена. Таким образом, ваш атрибут Projects меняется на:

Projects="@(Base->'%(FullPath)')\DebugConsoleApp\DebugConsoleApp.csproj" 

Поскольку Projects является массивом, MSBuild попытается выполнить преобразование для предоставленного выражения. Поскольку это недопустимое преобразование, возникает ошибка. Который является ошибкой, которую вы получаете.

Теперь, чтобы обойти это, вы можете изменить свою цель сборки, чтобы она выглядела так:

<Target Name="Build">
  <PropertyGroup>
    <_BaseDir>$(BaseDirectory)</_BaseDir>
    <_DeployDir>@(Base->'%(FullPath)')</_DeployDir>
  </PropertyGroup>

  <Message Text="_BaseDir: $(_BaseDir)"/>
  <Message Text="DeployDirectory: $(DeployDirectory)"/>

  <MSBuild Projects="$(_BaseDir)\DebugConsoleApp\DebugConsoleApp.csproj"
            Properties="Configuration=$(Configuration);OutputPath=$(_Tmp2)"
            ContinueOnError="false" />
  <!--<MSBuild Projects="$(BaseDirectory)\DebugConsoleApp\DebugConsoleApp.csproj" 
            Properties="Configuration=$(Configuration);OutputPath=$(DeployDirectory)" 
            ContinueOnError="false" />-->
</Target>

При таком подходе я создал группу свойств внутри самой цели и присвоил значение этих "виртуальных свойств" новым свойствам. Эти новые свойства - это не виртуальные свойства, а реальные свойства, поэтому вы можете использовать их, как и ожидалось, без проблем.

Теперь перейдем к вашему вопросу: "Почему задача сообщения работает WTF?!!!" Внутри цели Hello у вас есть следующее:

<Message Text="Hello world. BaseDirectory=$(BaseDirectory), DeployDirectory=$(DeployDirectory)" />

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

<Message Text="Hello world. BaseDirectory=@(Base->'%(FullPath)'), DeployDirectory=@(Base->'%(FullPath)')\Deploy" />

Хорошо, держи эту мысль.

Text Свойство задачи MSBuild определяется как строка, которая является скалярным значением. Если вы помните, свойство Projects в задаче MSBuild определено как ITaskItem[], так как это массив, это векторное значение. Когда @(...) находится внутри свойства векторных значений, для которого все выражение используется в качестве преобразования элемента. В этом случае утверждение @(Base->'%(FullPath)')\DebugConsoleApp\DebugConsoleApp.csproj не является допустимым выражением преобразования. Когда '@(..)' находится внутри объявления свойства скалярных значений, значения объединяются в строку. Таким образом, каждый экземпляр @(...)'обрабатывается и объединяется в одно строковое значение. Если есть несколько значений, то используются разделители.

Надеюсь, это объясняет поведение, которое вы видите, и это может быть ошибкой. Вы можете войти в него по адресу http://connect.microsoft.com/ и команда MSBuild выполнит его сортировку.

Подробнее о виртуальных свойствах Ранее я упоминал, что эти виртуальные свойства не ведут себя как обычные свойства в том смысле, что значение не ищется, а вместо этого использование $(...) заменяется выражением свойств. Не верьте мне на слово, посмотрите сами. Вот пример файла, который я создал

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
  <ItemGroup>
    <MyItem Include="C:\temp\01.txt"></MyItem>
  </ItemGroup>

  <PropertyGroup>
    <MyProperty>@(MyItem->'%(FullPath)')</MyProperty>
  </PropertyGroup>

  <Target Name="Demo">
    <Message Text="MyProperty: $(MyProperty)" />
    <!-- Add to the item -->
    <ItemGroup>
      <MyItem Include="C:\temp\01.txt"></MyItem>
    </ItemGroup>
    <Message Text="MyProperty: $(MyProperty)" />
  </Target>

</Project>

Здесь у меня есть список элементов, объявленный MyItem и зависимое свойство MyProperty. Внутри цели Demo я печатаю значение для MyProperty, затем добавляю другое значение в список элементов MyItem и снова распечатываю значение для MyProperty. Вот результат.

PS C:\temp\MSBuild\SO> msbuild .\Build.proj /nologo
Build started 4/26/2011 10:17:08 PM.
Project "C:\temp\MSBuild\SO\Build.proj" on node 1 (default targets).
First:
  MyProperty: C:\temp\01.txt
  MyProperty: C:\temp\01.txt;C:\temp\01.txt
Done Building Project "C:\temp\MSBuild\SO\Build.proj" (default targets).

Как вы можете видеть, он ведет себя так, как я сказал.

Вы боретесь с оценкой заказа. Переместите объявление группы свойств внутри цели "Hello", и она будет работать так, как вы ожидаете. Еще лучше, переместите его в свою собственную цель и задайте эту цель в любых DependsOnTargets для других целей, которые требуют выполнения оценки перед их выполнением, или, наоборот, установите эти цели как "BeforeTargets" для новой цели.

(редактировать)

Это будет работать для всех целей:

<ItemGroup>
  <Base Include="$(MSBuildProjectDirectory)\.." />
</ItemGroup>

<Target Name="Deploy" DependsOnTargets="Hello;Clean;Build" />

<Target Name="CalcProps">
  <PropertyGroup>
    <BaseDirectory>@(Base->'%(FullPath)')</BaseDirectory>
    <DeployDirectory>$(BaseDirectory)\Deploy</DeployDirectory>
    <Configuration>Release</Configuration>
  </PropertyGroup>
</Target>

<Target Name="Hello" DependsOnTargets="CalcProps">
  <Message
    Text="Hello world. BaseDirectory=$(BaseDirectory), DeployDirectory=$(DeployDirectory)"
    />
</Target>

<Target Name="Clean" DependsOnTargets="CalcProps">
  <RemoveDir Directories="$(DeployDirectory)" />
</Target>

<Target Name="Build" DependsOnTargets="CalcProps">
  <MSBuild 
    Projects="$(BaseDirectory)\DebugConsoleApp\DebugConsoleApp.csproj" 
    Properties="Configuration=$(Configuration);OutputPath=$(DeployDirectory)" 
    ContinueOnError="false"
    />  
</Target>

Я бы предположил, что при оценке аргумента Projects для задачи MSBuild, поскольку он имеет тип ITaskItem[], возможно, используется недооцененная строка в $(BaseDirectory), и поскольку это преобразование элемента, происходит сбой, поскольку в случай, когда преобразуемый элемент имеет более одного члена (хотя в этом случае это не так). Использование вами одного и того же свойства в задаче "Сообщение" передается аргументу типа System.String, который может иметь другую последовательность оценки.

Другие вопросы по тегам