经过大量关于项目组、目标和复制任务的阅读,我已经弄清楚了如何做我需要的事情。
<ItemGroup>
<FilesToCopy Include="..\**\app.template.config">
<NewFilename>app.config</NewFilename>
</FilesToCopy>
<FilesToCopy Include="..\**\web.template.config">
<NewFilename>web.config</NewFilename>
</FilesToCopy>
<FilesToCopy Include"..\Hibernate\hibernate.cfg.template.xml">
<NewFilename>hibernate.cfg.xml</NewFilename>
</FilesToCopy>
</ItemGroup>
<Target Name="CopyFiles"
Inputs="@(FilesToCopy)"
Outputs="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFilename)')">
<Message Text="Copying *.template.config files to *.config"/>
<Copy SourceFiles="@(FilesToCopy)"
DestinationFiles="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFilename)')"/>
我创建了一个包含要复制的文件的项组。 **操作符告诉它递归遍历整个目录树,查找指定名称的每个文件。 然后,我向这些文件中添加了一个名为“NewFilename”的元数据。 这就是我将重命名每个文件的名称。
此代码片段添加了目录结构中名为app.template.config的每个文件,并指定我将命名新文件为app.config:
<FilesToCopy Include="..\**\app.template.config">
<NewFilename>app.config</NewFilename>
</FilesToCopy>
我随后创建了一个目标来复制所有文件。这个目标最初非常简单,只调用了Copy任务以始终复制并覆盖文件。我将FilesToCopy项目组作为复制操作的源传递。我使用transforms指定输出文件名,以及我的NewFilename元数据和众所周知的项目元数据。
以下片段将例如将文件c:\Project\Subdir\app.template.config转换为c:\Project\Subdir\app.config,并将前者复制到后者:
<Target Name="CopyFiles">
<Copy SourceFiles="@(FilesToCopy)"
DestinationFiles="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFileName)')"/>
</Target>
但是,我注意到开发人员可能不希望每次运行脚本时都被覆盖他的定制化web.config文件。但是,如果存储库的web.template.config已被修改并且具有代码需要的新值,则开发人员可能应该覆盖他的本地文件。我尝试了多种不同的方法来实现这一点,如将Copy属性“SkipUnchangedFiles”设置为true,使用“Exist()”函数等,但都无济于事。
解决方案是
增量构建。这确保只有在app.template.config更新时才会覆盖文件。我将文件名称作为目标输入传递,并将新文件名称指定为目标输出。
<Target Name="CopyFiles"
Input="@(FilesToCopy)"
Output="@(FilesToCopy->'%(RootDir)%(Directory)%(NewFileName)')">
...
</Target>
这里的目标是检查当前输出是否与输入保持最新状态。如果不是,即.template.config文件比其对应的.config文件有更改,则会将web.template.config复制到现有的web.config上。否则,它将保留开发人员的web.config文件不变。如果没有需要复制的指定文件,则跳过该目标。在干净的代码库克隆后,每个文件都将被复制。
以上结果非常令人满意,因为我刚开始使用MSBuild,并对其强大的功能感到惊讶。唯一让我不喜欢的是我必须在两个地方重复完全相同的转换。我讨厌复制任何类型的代码,但我无法想出如何避免这种情况。如果有人有提示,将不胜感激。此外,虽然我认为需要这样做的开发实践非常糟糕,但这确实有助于减轻这种糟糕因素。