为什么MSBuild不能按照我的期望进行复制?

8
我需要编写自动构建脚本。由于其与VS.net的集成,我使用MSBUILD。我正在尝试将一些文件从构建环境复制到部署文件夹。我使用MSBuild的复制任务。但是,它不像我预期的那样复制目录树,而是将所有内容都复制到单个文件夹中。重复一遍,目录树中的所有文件最终都在一个文件夹中。我需要将文件夹和目录的树形结构复制到目标文件夹中。我是否漏掉了什么?
以下是我的构建脚本的相关部分:
<PropertyGroup>
    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
    <Source>outputfolder</Source>
    <DestEnv>x</DestEnv>
    <DeployPath>\\networkpath\$(DestEnv)</DeployPath>
</PropertyGroup>
<ItemGroup>
    <TargetDir Include="$(DeployPath)\**\*" Exclude="**\web.config"></TargetDir>
    <SourceDir Include="$(Source)\**\*" />
</ItemGroup>    
<Target Name="Clean" >
    <!-- clean detail ... -->
</Target>
<Target Name="migrate" DependsOnTargets="Clean">
    <Copy DestinationFolder="$(DeployPath)" SourceFiles="@(SourceDir)" />
</Target>
4个回答

15

在调试MSBuild复制问题时,我们发现了一个gem:

http://blog.scrappydog.com/2008/06/subtle-msbuild-bug-feature.html

ItemGroups在Targets之前被解析,因此当进一步引用脚本中的ItemGroup时,任何创建新文件(例如编译!)的目标都不会被捕捉到。

Eric Bowen还描述了这个“feature”的解决方法,即CreateItem任务:

<Target Name="Copy" >
    <CreateItem Include="..\Source\**\bin\**\*.exe"
        Exclude="..\Source\**\bin\**\*.vshost.exe">
        <Output TaskParameter="Include" ItemName="CompileOutput" />
    </CreateItem>
    <Copy SourceFiles="@(CompileOutput)" 
        DestinationFolder="$(OutputDirectory)"></Copy>
</Target>

非常赞赏他!


12

当您为复制任务指定DestinationFolder时,它会将SourceFiles集合中的所有项复制到DestinationFolder。这是可以预期的,因为复制任务无法确定每个项路径的哪个部分需要替换为DestinationFolder以保持树结构。例如,如果您的SourceDir集合定义如下:

<ItemGroup>
    <SourceDir Include="$(Source)\**\*" />
    <SourceDir Include="E:\ExternalDependencies\**\*" />
    <SourceDir Include="\\sharedlibraries\gdiplus\*.h" />
</ItemGroup>

你期望目标文件夹的目录树是什么样子?

为了保持目录树,需要进行身份转换,并为SourceFiles集合中的每个项目生成一个目标项。下面是一个示例:

<Copy SourceFiles="@(Compile)" DestinationFiles="@(Compile->'$(DropPath)%(Identity)')" />

复制任务将遍历 SourceFiles 集合中的每个项,并通过用 $(DropPath) 替换源项规范中 ** 之前的部分来转换其路径。

有人可能会认为,DestinationFolder 属性应该被写成以下转换的快捷方式:

<Copy SourceFiles="@(Compile)" DestinationFiles="@(Compile->'$(DestinationFolder)%(Identity)')" />

哎呀,这会防止你试图避免的深度复制到平面文件夹情况,但其他人可能会在他们的构建过程中使用。


5
非常简单的示例,递归地复制目录内容和结构:
<Copy SourceFiles="@(Compile)" DestinationFolder="c:\foocopy\%(Compile.RecursiveDir)"></Copy>

@(Compile)是一个ItemGroup,包含你想要复制的所有文件。可以是以下类似内容:

   <ItemGroup>
      <Compile Include=".\**\*.dll" /> 
   </ItemGroup>

复制任务将像xcopy一样将所有文件复制到c:\foocopy。

<Copy SourceFiles="$(SourceViewsLocation)" DestinationFolder="$(RepositoryLocation)\%(RecursiveDir)"></Copy> 出现错误:MSBUILD: error MSB4095:正在引用项目元数据%(RecursiveDir),但未指定项目名称。请使用%(itemname.RecursiveDir)指定项目名称。 - Boris Callens
我不知道发生了什么。 - Boris Callens
1
应该修复这个Bug... DestinationFolder的值的替代语法:DestinationFolder="@(Compile->'c:\foocopy%(RecursiveDir)')" - Adam

4

我在MSDN的示例中找到了一个示例,虽然我不太理解,但我会在这里给我的stackoverflow伙伴留下一个示例。这是上面迁移目标的修复版本:

<Target Name="migrate" DependsOnTargets="Clean">
    <Copy DestinationFiles="@(SourceDir->'$(DeployPath)\%(RecursiveDir)%(Filename)%(Extension)')" SourceFiles="@(SourceDir)" />
</Target>

如果有人真正理解这个例子,请解释一下。祝你好运!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接