我有以下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-&gt;'%(FullPath)')\ Deploy”不能在此使用 上下文。项目列表不能与项目lis的其他字符串连接 预期。使用分号分隔多个项目列表。
为什么我会收到此错误以及如何避免错误?我在Base
中使用了项ItemGroup
,因为我需要在路径中删除..
,Items
允许通过%FullPath
元数据执行此操作。如果我只使用PropertyGroup
,那么一切正常,但我在所有路径中都有..
。
答案 0 :(得分:3)
很难确切地说出引擎盖下发生了什么。我不在MSBuild上,所以我只是非常熟悉实际的实现。我们需要一个MSBuild dev来进行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->'%(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).
正如你所看到的,它的行为方式与我所陈述的一致。
答案 1 :(得分: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类型的参数,该参数可能具有不同的评估序列。