在TFS 2010或msbuild中触发配置转换

16

我正试图在持续集成环境中利用配置转换

我需要一种告诉 TFS build 代理执行转换的方法。我希望发现配置转换文件(例如 web.qa-release.config、web.production-release.config 等)后,它就可以直接运行。但实际情况并非如此。

我有一个 TFS 构建定义,构建正确的配置(qa-release、production-release 等),我还有一些在这些定义中构建的特定 .proj 文件,其中包含一些特定于环境的参数,例如:

<PropertyGroup Condition=" '$(Configuration)'=='production-release' ">
    <TargetHost Condition=" '$(TargetHost)'=='' ">qa.web</TargetHost>
    ...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)'=='qa-release' ">
    <TargetHost Condition=" '$(TargetHost)'=='' ">production.web</TargetHost>
    ...
</PropertyGroup>

从输出结果来看,正确的配置已经被构建出来了。现在我只需要学习如何触发配置转换。在最终的 .proj 构建文件中是否可以添加一些神奇的内容来启动转换并删除单个的转换文件呢?

6个回答

11

我找到了另一种方法来实现这个目标,而不是创建自定义活动。你只需要修改正在构建的Web应用程序的Visual Studio项目文件即可。

添加以下内容(可以在项目文件末尾找到“AfterBuild”目标的占位符):

<Target Name="AfterBuild" Condition="$(IsAutoBuild)=='True'"> 
  <ItemGroup> 
         <DeleteAfterBuild Include="$(WebProjectOutputDir)\Web.*.config" /> 
  </ItemGroup> 
  <TransformXml Source="Web.config" Transform="$(ProjectConfigTransformFileName)" Destination="$(WebProjectOutputDir)\Web.config"/> 
  <Delete Files="@(DeleteAfterBuild)" />
</Target> 

然后您只需将/p:IsAutoBuild="True"添加到构建定义中的“高级”部分中找到的“MSBuild Arguments”字段中。

这将强制TFS 2010在进行构建时对web.config进行转换。

有关详细信息,请参见Kevin Daly的博客


6

在Visual Studio 2010中添加到网站项目的web.config转换功能在命令行和TFS构建中默认处于禁用状态。

有两个相对简单的解决方案:

选项1:编辑构建定义,并将以下内容添加到“MSBuild Arguments”字段中:

/p:UseWPP_CopyWebApplication=true /p:PipelineDependsOnBuild=false

使用UseWPP_CopyWebApplication将启用新的Web发布管道(WPP)进行构建。WPP执行web.config转换,并且还可以用来阻止诸如.PDB文件被复制到bin文件夹等操作。

选项2:MSBuild和WPP都是可完全扩展的。在与项目相同的目录中创建一个新的XML文件,并使用“.targets”扩展名,例如ProjectName.custom.targets。将以下MSBuild代码放入目标文件中:

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <UseWPP_CopyWebApplication>True</UseWPP_CopyWebApplication>
    <PipelineDependsOnBuild>False</PipelineDependsOnBuild>
  </PropertyGroup>
</Project>

右键单击您的网站,选择“卸载项目”。右键单击已卸载的项目并选择“编辑”。滚动到项目文件底部,查找以下行:

<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

这些行是C#和Web项目构建过程的钩子。在CSharp导入之前立即插入自定义构建扩展(目标文件)的导入:

<Import Project="ProjectName.custom.targets"/>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

好了,你可以开始了。使用MSBuild自定义方法需要更多的设置工作,但好处是你可以使用新的目标文件来“挂钩”构建过程,并在服务器上更好地控制构建方式。例如,你可以挂钩任务来执行CSS和JS压缩。

我还建议查看“wpp targets” - 如果你使用特定名称“ProjectName.wpp.targets”命名另一个MSBuild文件,你可以控制整个网站发布过程。我们使用它来删除 -vsdoc JavaScript文档文件,因为发布的网站输出被复制:

<ItemGroup>
  <ExcludeFromPackageFiles Include="Scripts\**\*-vsdoc.js;Resources\Scripts\**\-vsdoc.js">
    <FromTarget>Project</FromTarget>
  </ExcludeFromPackageFiles>
</ItemGroup>

总的来说,最好完全不要在构建过程中包含生产环境的web.config文件。我们直接将转换放在我们的生产部署机器上,并使用PowerShell在应用程序部署时进行转换。


6

我终于成功了。我使用的是TFS 2008,但也使用了MSBuild 4.0,所以这对你来说应该也有效。

首先,在TFSBuild.proj中添加此导入:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

接下来,添加一个 BeforeDropBuild 目标:
<Target Name="BeforeDropBuild">
  <TransformXml Source="$(SolutionRoot)\MySite\Web.config"
    Transform="$(SolutionRoot)\MySite\Web.QA.config"
    Destination="$(OutDir)\_PublishedWebsites\MySite\Web.QA.config.transformed" />
</Target>

您可以将Web.QA.config.transformed复制到需要的任何位置。


TFSBuild.proj在TFS 2010中已经不存在,这个问题与它有关。现在他们使用基于工作流的新构建系统。我还没有弄清楚如何转换web.config,因为我还需要做这个... - Steve Ward
1
我们刚刚升级到了TFS 2010。负责升级的顾问设置了一些向后兼容的工作流程,目前使用我们现有的TFSBuild.proj文件。虽然我们以后可能会后悔,但目前这对于我们来说仍然可以在Team Build 2010中使用... - Rohan Singh
3
升级过程中,向后兼容性功能已被自动安装。微软将其保留在原地,以应对此类情况。 - NotMe

5
以下是更简单的答案:)
从以下链接获取更多信息(以防链接被移动/404等): http://social.msdn.microsoft.com/Forums/en-US/tfsbuild/thread/d5c6cc7b-fbb1-4299-a8af-ef602bad8898/

Here is how I solved this. The key was to edit the *.csproj file on the website and add the following to the AfterBuild target (be sure to move the end comment above it). This is for our website build project in Team Foundation Server.

<Target Name="AfterBuild">
    <TransformXml Condition="Exists('$(OutDir)\_PublishedWebsites\$(TargetName)')"
                  Source="Web.config"
                  Transform="$(ProjectConfigTransformFileName)"
                  Destination="$(OutDir)\_PublishedWebsites\$(TargetName)\Web.config" />
</Target>

To keep the web.debug.config, web.release.config, etc... from being published be sure to set the "Build Action" in the properties window for each of the config transformation files to "None". Only the main web.config should have a "Build Action" of "Content"

One easy way to edit the csproj file is to load either the "PowerCommands for Visual Studio 2010" or "Productivity Power Tools" Extension to Visual Studio 2010 available from the Visual Studio Gallery. Once loaded all you have to do is right click on the project in your solution and select "Unload Project". Then you can right click again and select "Edit..." to edit the csproj file XML directly. Then when done just right click again and select "Reload Project".


这样做的好处是除了TFS之外还可以与其他构建引擎一起使用 - 例如,我正在使用CC.NET和Uppercut。 - David Keaveny

1

要在WorkFlow中完成此操作,您需要创建一个自定义活动。这里有一篇相当不错的文章here

对于这个特定的活动,您需要创建一个Activity项目(将其从.Net 4客户端配置文件更改为.Net 4),并从GAC引用Microsoft.Build.FrameworkMicrosoft.Build.Utilities.v4.0,然后从%programfiles%\msbuild\Microsoft\VisualStudio\v10.0\WebApplications(如果您使用的是64位系统,则为%programfiles(x86)%)引用Microsoft.Web.Publishing.Tasks

完成后,您需要添加这两个类:

首先,有一个存根:

internal class BuildEngineStub : IBuildEngine
{
    public bool BuildProjectFile(string projectFileName, string[] targetNames, System.Collections.IDictionary globalProperties, System.Collections.IDictionary targetOutputs)
    {
        throw new NotImplementedException();
    }

    public int ColumnNumberOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }

    public bool ContinueOnError
    {
        get { throw new NotImplementedException(); }
    }

    public int LineNumberOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }

    public void LogCustomEvent(CustomBuildEventArgs e)
    {
    }

    public void LogErrorEvent(BuildErrorEventArgs e)
    {
    }

    public void LogMessageEvent(BuildMessageEventArgs e)
    {
    }

    public void LogWarningEvent(BuildWarningEventArgs e)
    {
    }

    public string ProjectFileOfTaskNode
    {
        get { throw new NotImplementedException(); }
    }
}

然后是活动类本身:

[BuildActivity(HostEnvironmentOption.Agent)]
public sealed class WebConfigTransform : CodeActivity
{
    private const string WEB_CONFIG = "Web.config";
    private const string WEB_CONFIG_TRANSFORM_FORMAT = "Web.{0}.config";

    private IBuildEngine _buildEngine { get { return new BuildEngineStub(); } }

    [RequiredArgument]
    public InArgument<string> TransformationName { get; set; }
    [RequiredArgument]
    public InArgument<string> SourceFolder { get; set; }
    [RequiredArgument]
    public InArgument<string> DestinationFolder { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        var transformationName = context.GetValue(this.TransformationName);
        var sourceFolder = context.GetValue(this.SourceFolder);
        var destinationFolder = context.GetValue(this.DestinationFolder);

        var source = Path.Combine(sourceFolder, WEB_CONFIG);
        var destination = Path.Combine(destinationFolder, WEB_CONFIG);
        var destinationbackup = string.Format("{0}.bak", destination);
        var transform = Path.Combine(sourceFolder, string.Format(WEB_CONFIG_TRANSFORM_FORMAT, transformationName));

        if(!File.Exists(source))
            throw new ArgumentException("Web.config file doesn't exist in SourceFolder");
        if (!File.Exists(transform))
            throw new ArgumentException("Web.config transformation doesn't exist in SourceFolder");
        if (File.Exists(destination))
        {
            File.Copy(destination, destinationbackup);
            File.Delete(destination);
        }

        var transformation = new TransformXml();
        transformation.Source = new TaskItem(source);
        transformation.Destination = new TaskItem(destination);
        transformation.Transform = new TaskItem(transform);
        transformation.BuildEngine = _buildEngine;

        if (transformation.Execute())
        {
            File.Delete(destinationbackup);
        }
        else
        {
            File.Copy(destinationbackup, destination);
            File.Delete(destinationbackup);
        }
    }
}

BuildEngineStub 的原因在于 TransformXml 类使用它进行日志记录。

你需要注意的唯一一件事是 TransformXml.Execute 函数会在构建过程完成之前锁定源配置文件。


1
这个东西太复杂了,而且已经有现成的解决方案了。你可能想看一下被接受的答案。 - NotMe

1

你需要做的就是在TFS构建定义中设置应该使用哪个配置。

  1. 进入Team Explorer > Builds
  2. 编辑你的构建定义(或创建新的)
  3. 在“Process”步骤下,有一个“Configurations to Build”的设置。

在我的情况下,我已经为CI专门设置了一个配置,然后执行正确的web.config转换。确保你已经添加了“CI”转换文件,然后你就可以开始了。


请注意,web.config转换仅在部署期间调用,而不是在标准构建期间调用。 - Wallace Breza
4
我有一个 Release 配置 (AnyCpu|Release),但构建中的 Web.config 没有更改。 - Juan Zamudio
1
这是错误的,那些“配置”是构建配置,而不是配置文件。 - Alex

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