MSBuild:以通用方式复制多个目录
MSBuild: Copy multiple directories in a generic way
我有一个包含多个 Web 项目(例如 WebappA、WebappB、WebappC)的 Visual Studio 解决方案。当 TFS 构建解决方案时,它会将构建结果放在 _PublishedWebsites 文件夹中。文件夹结构可能如下所示:
$(OutDir)
|
+-- _PublishedWebsites
|
+-- WebappA
|
+-- WebappA_Package
|
+-- WebappB
|
+-- WebappB_Package
|
+-- WebappC
|
+-- WebappC_Package
我想以 zip 文件的形式为我们的运营部门构建一个部署包。因此,我让 TFS 运行 一个 MSBuild 脚本,该脚本将 _Package 文件夹复制到一个自定义目录结构中,该目录结构在后续步骤中被压缩。
$(PackageDirectory)
|
+-- Web
|
+-- WebappA
|
+-- WebappB
|
+-- WebappB
我能够创建一堆执行复制操作的 MSBuild 目标。但我对我的解决方案不满意。我以明确的方式引用每个 webapp,这就是为什么我最终得到很多重复代码的原因。更糟的是,每次添加新的 Web 应用程序时,我都必须扩展构建脚本。
<Target Name="Pack" DependsOnTargets="Pack-WebappA;Pack-WebappB;Pack-WebappC" />
<Target Name="Pack-WebappA">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappA_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappA\" />
</Target>
<Target Name="Pack-WebappB">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappB_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappB\" />
</Target>
<Target Name="Pack-WebappC">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappC_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappC\" />
</Target>
我正在寻找一种解决方案,该解决方案以通用方式完成所有事情,而无需引用具体的网络应用程序。本质上,MSBuild 应该做的就是查看 _PublishedWebsites 文件夹,将每个带有 _Package 后缀的子文件夹复制到另一个文件夹,然后删除后缀。这听起来很简单,但我无法想出一个可行的解决方案。我试过批处理,但没有成功。
您对当前的解决方案不满意,这很正确:必须将此类事情自动化。我同意 MsBuild 并不总是让它变得直截了当;批处理是正确的方法,但您必须添加一些 filtering/manipulating 的项目。使用 property functions 这并不难:
In essence all what MSBuild should do is to look into the
_PublishedWebsites folder and copy each subfolder with a _Package suffix to another folder and remove the suffix.
我们将其翻译为:
- 列出 _PublishedWebsites 中的所有目录
- 筛选列表并仅包含以 _Package 结尾的列表
- 列出这些目录中的所有文件,并将它们的目标设置为已删除后缀的 PackageDirectory 的子目录
- 复制每个文件到相应的目录
第 3 步实际上是两件事(列表+指定目录),因为这是典型的 msbuild 做事方式。还有其他方法可以做到这一点,但这个似乎适合这里。
<Target Name="BatchIt">
<PropertyGroup>
<SourceDir>$(OutDir)_PublishedWebsites\</SourceDir>
<DestDir>$(PackageDirectory)Web\</DestDir>
<PackageString>_Package</PackageString>
</PropertyGroup>
<ItemGroup>
<!-- step 1 -->
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*`, System.IO.SearchOption.AllDirectories ) )"/>
<!-- step 2 -->
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(Filename), '.*$(PackageString)' ) )"/>
<!-- step 3 -->
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>$(PackageDirectory)$([System.IO.Path]::GetFilename( %(PackageDirs.Identity) ).Replace( $(PackageString), '' ) )</DestDir>
</PackageFiles>
</ItemGroup>
<!-- step 4 -->
<Copy SourceFiles="%(PackageFiles.Identity)" DestinationFolder="%(PackageFiles.DestDir)" />
</Target>
edit 一种更高效且可能更合乎逻辑的方法是在构建 PackageDir 列表时直接指定 DestDir:
<ItemGroup>
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*`, System.IO.SearchOption.AllDirectories))"/>
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(Filename), '.*$(PackageString)' ))">
<DestDir>$(DestDir)$([System.IO.Path]::GetFilename( %(Dirs.Identity) ).Replace( $(PackageString), '' ))</DestDir>
</PackageDirs>
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>%(PackageDirs.DestDir)</DestDir>
</PackageFiles>
</ItemGroup>
stijn 给出了一个很好的答案。它几乎对我有用。我只改变了一两件事。至少在我的情况下,这是可以解决问题的代码。
<Target Name="PackWeb">
<PropertyGroup>
<SourceDir>$(OutDir.TrimEnd('\'))\_PublishedWebsites\</SourceDir>
<PackDir>$(PackageDirectory.TrimEnd('\'))\Web\</PackDir>
<PackageString>_Package</PackageString>
</PropertyGroup>
<ItemGroup>
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*` ) )"/>
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(FullPath), '.*$(PackageString)' ) )"/>
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>$(PackDir)$([System.IO.Path]::GetFilename( %(PackageDirs.Identity) ).Replace( $(PackageString), '' ) )</DestDir>
</PackageFiles>
</ItemGroup>
<Copy SourceFiles="%(PackageFiles.Identity)" DestinationFolder="%(PackageFiles.DestDir)" />
</Target>
我有一个包含多个 Web 项目(例如 WebappA、WebappB、WebappC)的 Visual Studio 解决方案。当 TFS 构建解决方案时,它会将构建结果放在 _PublishedWebsites 文件夹中。文件夹结构可能如下所示:
$(OutDir)
|
+-- _PublishedWebsites
|
+-- WebappA
|
+-- WebappA_Package
|
+-- WebappB
|
+-- WebappB_Package
|
+-- WebappC
|
+-- WebappC_Package
我想以 zip 文件的形式为我们的运营部门构建一个部署包。因此,我让 TFS 运行 一个 MSBuild 脚本,该脚本将 _Package 文件夹复制到一个自定义目录结构中,该目录结构在后续步骤中被压缩。
$(PackageDirectory)
|
+-- Web
|
+-- WebappA
|
+-- WebappB
|
+-- WebappB
我能够创建一堆执行复制操作的 MSBuild 目标。但我对我的解决方案不满意。我以明确的方式引用每个 webapp,这就是为什么我最终得到很多重复代码的原因。更糟的是,每次添加新的 Web 应用程序时,我都必须扩展构建脚本。
<Target Name="Pack" DependsOnTargets="Pack-WebappA;Pack-WebappB;Pack-WebappC" />
<Target Name="Pack-WebappA">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappA_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappA\" />
</Target>
<Target Name="Pack-WebappB">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappB_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappB\" />
</Target>
<Target Name="Pack-WebappC">
<ItemGroup>
<WebAppFile Include="$(OutDir)_PublishedWebsites\WebappC_Package\*.*" />
</ItemGroup>
<Copy SourceFiles="@(WebAppFile)" DestinationFolder="$(PackageDirectory)Web\WebappC\" />
</Target>
我正在寻找一种解决方案,该解决方案以通用方式完成所有事情,而无需引用具体的网络应用程序。本质上,MSBuild 应该做的就是查看 _PublishedWebsites 文件夹,将每个带有 _Package 后缀的子文件夹复制到另一个文件夹,然后删除后缀。这听起来很简单,但我无法想出一个可行的解决方案。我试过批处理,但没有成功。
您对当前的解决方案不满意,这很正确:必须将此类事情自动化。我同意 MsBuild 并不总是让它变得直截了当;批处理是正确的方法,但您必须添加一些 filtering/manipulating 的项目。使用 property functions 这并不难:
In essence all what MSBuild should do is to look into the _PublishedWebsites folder and copy each subfolder with a _Package suffix to another folder and remove the suffix.
我们将其翻译为:
- 列出 _PublishedWebsites 中的所有目录
- 筛选列表并仅包含以 _Package 结尾的列表
- 列出这些目录中的所有文件,并将它们的目标设置为已删除后缀的 PackageDirectory 的子目录
- 复制每个文件到相应的目录
第 3 步实际上是两件事(列表+指定目录),因为这是典型的 msbuild 做事方式。还有其他方法可以做到这一点,但这个似乎适合这里。
<Target Name="BatchIt">
<PropertyGroup>
<SourceDir>$(OutDir)_PublishedWebsites\</SourceDir>
<DestDir>$(PackageDirectory)Web\</DestDir>
<PackageString>_Package</PackageString>
</PropertyGroup>
<ItemGroup>
<!-- step 1 -->
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*`, System.IO.SearchOption.AllDirectories ) )"/>
<!-- step 2 -->
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(Filename), '.*$(PackageString)' ) )"/>
<!-- step 3 -->
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>$(PackageDirectory)$([System.IO.Path]::GetFilename( %(PackageDirs.Identity) ).Replace( $(PackageString), '' ) )</DestDir>
</PackageFiles>
</ItemGroup>
<!-- step 4 -->
<Copy SourceFiles="%(PackageFiles.Identity)" DestinationFolder="%(PackageFiles.DestDir)" />
</Target>
edit 一种更高效且可能更合乎逻辑的方法是在构建 PackageDir 列表时直接指定 DestDir:
<ItemGroup>
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*`, System.IO.SearchOption.AllDirectories))"/>
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(Filename), '.*$(PackageString)' ))">
<DestDir>$(DestDir)$([System.IO.Path]::GetFilename( %(Dirs.Identity) ).Replace( $(PackageString), '' ))</DestDir>
</PackageDirs>
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>%(PackageDirs.DestDir)</DestDir>
</PackageFiles>
</ItemGroup>
stijn 给出了一个很好的答案。它几乎对我有用。我只改变了一两件事。至少在我的情况下,这是可以解决问题的代码。
<Target Name="PackWeb">
<PropertyGroup>
<SourceDir>$(OutDir.TrimEnd('\'))\_PublishedWebsites\</SourceDir>
<PackDir>$(PackageDirectory.TrimEnd('\'))\Web\</PackDir>
<PackageString>_Package</PackageString>
</PropertyGroup>
<ItemGroup>
<Dirs Include="$([System.IO.Directory]::GetDirectories( `$(SourceDir)`, `*` ) )"/>
<PackageDirs Include="%(Dirs.Identity)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch( %(FullPath), '.*$(PackageString)' ) )"/>
<PackageFiles Include="%(PackageDirs.Identity)\*.*">
<DestDir>$(PackDir)$([System.IO.Path]::GetFilename( %(PackageDirs.Identity) ).Replace( $(PackageString), '' ) )</DestDir>
</PackageFiles>
</ItemGroup>
<Copy SourceFiles="%(PackageFiles.Identity)" DestinationFolder="%(PackageFiles.DestDir)" />
</Target>