MSBuild 项目无法在 MSBuild 任务中使用,错误 MSB4012

发布于 2024-11-01 19:18:29 字数 1608 浏览 0 评论 0原文

我有以下 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 中使用 item Base ,因为我需要删除路径中的 .. ,并且 Items 允许通过 %FullPath 元数据来完成此操作。如果我只使用 PropertyGroup 那么一切工作正常,但我在所有路径中都有这个 ..

I have the following MSBuild project file:

<?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>

And when I run it I get an error:

C:\Repositories\Project\Build\Build.proj(22,16): error
MSB4012: The expression "@(Base->'%(FullPath)')\Deploy" cannot be used in this
context. Item lists cannot be concatenated with other strings where an item lis
t is expected. Use a semicolon to separate multiple item lists.

Why do I get this error and how can it be avoided? I use item Base within ItemGroup because I need to get rid of .. in path, and Items allow to do it via %FullPath metadata. If I use just PropertyGroup then everything works fine, but I have this .. in all paths.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

白云不回头 2024-11-08 19:18:29

很难准确说出幕后发生的事情。我不在 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" />

好吧,保留这个想法。

MSBuild 任务上的 Text 属性定义为一个字符串,它是一个标量值。如果您还记得 MSBuild 任务上的 Projects 属性定义为 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).

正如您所看到的,它的行为方式与我所说的一致。

it is tough to tell exactly what is happening underneath the hood. I am not on the MSBuild so I am only loosly familiar with the actual implementation. We would need an MSBuild dev to chime in for a 100% correct answer. But here is what I'm assuming is happening (read: the remainder of this contains speculation on my part).

Inside you target when you use the statement

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

MSBuild notices that you have the property expansion $(BaseDirectory) used and that the parameter type for Projects on the MSBuild is an array. Also MSBuild notices that BaseDirectory is a property which contains an item. These properties do not behave like normal properties. You can think of them as "virtual properties" (yes I just made up that term). When these properties are used instead of looking up the value, there is a replacement made inline. So your Projects attribute changes to:

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

Since Projects is an array MSBuild will attempt to perform a transformation on the expression provided. Since that is not a valid transformation an error occurs. Which is the error that you are receiving.

Now to work around this you can change you Build target to look like:

<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>

With this approach I have created a property group within the target itself and assigned the value of those "virtual properties" into new properties. Those new properties are not virtual properties, but real properties so you can use them as you expected with no issues.

Now on to your question, "why does the message task work WTF?!!!"
Inside the Hello target you have the following:

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

Which works without no problems. Previously I mentioned that these virtual properties will essentially be replaced inline with the definition backing them, so this would in effect become.

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

OK hold that thought.

The Text property on the MSBuild task is defined as a string, which is a scalar value. If you recall the Projects property on the MSBuild task is defined as ITaskItem[], since this is an array its a vector value. When a @(...) is found within a vector values property the entire expression is used for as an item transformation. In this case the statement @(Base->'%(FullPath)')\DebugConsoleApp\DebugConsoleApp.csproj is not a valid transform expression. When a '@(..)' is found inside of a scalar values property declaration the values are flattened into a string. So each instance of '@(...)' is processed and flattened into a single string value. If there are multiple values then delimiters are used.

So hopefully that explains the behavior you are seeing, and it may actually be a bug. You can log it at http://connect.microsoft.com/ and the MSBuild team will triage it.

More on virtual properties
Earlier I mentioned that these virtual properties do not behave like normal properties in the sense the the value is not looked up, but instead the usage of $(...) is replaced with the properties expression. Don't take my word for it, see it for yourself. Here is a sample file that I created

<?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>

Here I have an item list MyItem declared and a dependent property MyProperty. Inside the Demo target I print the value for MyProperty then I add another value to the MyItem item list and print out the value for MyProperty again. Here is the result.

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).

As you can see it behaves in the way in which I stated.

尹雨沫 2024-11-08 19:18:29

您正在与评估订单作斗争。将您的属性组声明移至“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) 中未评估的字符串,并且因为它是一项项目转换,如果正在转换的项目有多个成员(即使在本例中没有),则会出错。您在消息任务中使用的同一属性将被传递给 System.String 类型的参数,该参数可能具有不同的计算顺序。

You're fighting evaluation ordering. Move your property group declaration inside the "Hello" target and it will work the way you expect. Better yet, move it into its own target, and set that target in any DependsOnTargets for other targets that require the evaluation to be performed before they execute, or conversely, set those targets as the "BeforeTargets" for your new target.

(edit)

This will work for all targets:

<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>

I'd theorize that the evaluation of the Projects argument to the MSBuild task, since it is of type ITaskItem[], may be using the unevaluated string in $(BaseDirectory), and since it is an item transform, erroring out since in the case where the item being transformed has more than one member (even though in this case it doesn't). Your use of the same property in a Message task is being passed to an argument of type System.String, which may have a different evaluation sequence.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文