【问题标题】:MSBuild item can't be used in MSBuild task, error MSB4012MSBuild 项目不能在 MSBuild 任务中使用,错误 MSB4012
【发布时间】:2011-04-16 19:58:10
【问题描述】:

我有以下 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”不能用于此 语境。项目列表不能与项目所在的其他字符串连接 t 是预期的。使用分号分隔多个项目列表。

为什么会出现此错误,如何避免?我在ItemGroup 中使用项目Base,因为我需要在路径中删除..,并且Items 允许通过%FullPath 元数据来实现。如果我只使用PropertyGroup,那么一切正常,但我在所有路径中都有这个..

【问题讨论】:

    标签: msbuild msbuild-propertygroup itemgroup


    【解决方案1】:

    很难准确说出引擎盖下发生的事情。我不在 MSBuild 上,所以我对实际的实现只是松散地熟悉。我们需要一个 MSBuild 开发人员来回答 100% 正确的答案。但这是我假设正在发生的事情(阅读:本文的其余部分包含我的猜测)。

    在你使用语句时的目标内部

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

    MSBuild 注意到您使用了属性扩展 $(BaseDirectory) 并且 MSBuild 上的 Projects 的参数类型是一个数组。 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" />
    

    好吧,保持这个想法。

    MSBuild 任务上的Text 属性被定义为一个字符串,它是一个标量值。如果您还记得 MSBuild 任务上的 Projects 属性被定义为 ITaskItem[],因为这是一个数组,它是一个向量值。当在向量值属性中找到@(...) 时,整个表达式将用作项目转换。在这种情况下,语句 @(Base-&gt;'%(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).
    

    如您所见,它的行为方式与我所说的一样。

    【讨论】:

      【解决方案2】:

      您正在与评估订购作斗争。将您的属性组声明移动到“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>
      

      我推测 MSBuild 任务的 Projects 参数的评估,因为它是 ITaskItem[] 类型,可能正在使用 $(BaseDirectory) 中未评估的字符串,并且因为它是一个项目转换,所以会出错因为在被转换的项目有多个成员的情况下(即使在这种情况下它没有)。您在 Message 任务中对相同属性的使用被传递给 System.String 类型的参数,该参数可能具有不同的评估顺序。

      【讨论】:

      • 但是 Hello 目标输出一切正确,我在屏幕上看到的正是我期望看到的。为什么它在 Hello 目标中起作用并导致在 Build 目标中出错?
      • 谢谢,您的建议是正确的,它有效。尽管我对其进行了一些改进:没有必要在每个任务之前计算属性,每个“根”任务只计算一次就足够了,即如果我要调用Deploy目标,那么 CalcProps 可以是放入Deploy 任务的DependsOnTargets 属性。其他任务是从Deploy 任务而不是直接从命令行调用的。
      • 实际上,我写的方式只会计算一次属性。 MSBuild 不会运行目标两次,它只会确保它在任何依赖目标首先运行之前运行一次。您可以通过在 CalcProps 目标中放置 Message 任务来验证这一点,然后调用 /t:Deploy,Message 只会出现一次。
      • 或者,我关心的不是多次评估,而是需要多次指定目标,我不想指定它,或者至少让它尽可能少。
      • 毕竟我使用Project 元素的InitialTargets 属性来重新计算属性。 InitialTargets 属性确保其中指定的目标在任何任务执行之前执行。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-06
      • 1970-01-01
      • 2012-03-16
      • 2012-03-19
      相关资源
      最近更新 更多