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

时间:2009-12-08 13:52:08

标签: .net tfs msbuild continuous-integration config

我试图在持续集成环境中使用configuration transformations

我需要一种方法来告诉TFS构建代理执行转换。我有点希望它在发现配置转换文件(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>

我从输出中知道正在构建正确的配置。现在我只需要学习如何触发配置转换。是否有一些hocus pocus,我可以添加到构建中的最终.proj以启动转换并吹走各个转换文件?

6 个答案:

答案 0 :(得分: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进行构建时,这将强制TFS 2010在web.config上进行转换。

更多详情可在Kevin Daly's Blog找到。

答案 1 :(得分: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复制到您需要的任何位置。

答案 2 :(得分:6)

默认情况下,对于命令行和TFS版本,已禁用添加到Visual Studio 2010中的网站项目的web.config转换功能。

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

选项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 Project构建过程被连接起来的地方。在 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.configs。我们将转换直接放在我们的生产部署机器上,并在部署应用程序时使用powershell进行转换。

答案 3 :(得分:5)

这是一个更简单的答案。 :)

http://social.msdn.microsoft.com/Forums/en-US/tfsbuild/thread/d5c6cc7b-fbb1-4299-a8af-ef602bad8898/

从链接(如果它被移动/ 404 /等):

  

以下是我解决这个问题的方法。钥匙   是编辑* .csproj文件   网站并添加以下内容   AfterBuild目标(一定要移动   结束评论上面)。这是为了   我们的网站在团队中建立项目   Foundation Server。

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

保留web.debug.config,   web.release.config等等...来自   发表时一定要设置“Build   动作“在属性窗口中   每个配置转换   文件为“无”。只有主力   web.config应该有一个“Build   “内容”的行动

     

编辑csproj文件的一种简单方法   是加载“PowerCommands”   对于Visual Studio 2010“或   “生产力电动工具”扩展   从Visual Studio 2010可用   Visual Studio库。一旦   加载所有你需要做的是对的   单击解决方案中的项目   并选择“卸载项目”。那么你   可以再次右键单击并选择   “编辑...”编辑csproj文件XML   直。然后做得恰到好处   再次单击并选择“重新加载”   项目”。

答案 4 :(得分:1)

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

  1. 转到团队资源管理器&gt;构建
  2. 编辑您的构建定义(或创建新的)
  3. 在“处理”步骤下,有“要构建的配置”的设置。
  4. 在我的情况下,我已经为CI设置了一个配置,然后执行正确的web.config转换。确保您已添加“CI”转换文件,您应该很高兴。

答案 5 :(得分:1)

要在WorkFlow中执行此操作,您必须创建自定义活动。关于它的文章非常好here

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

完成后,添加以下两个类:

首先,有一个存根:

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 函数会锁定源配置文件,直到构建过程完成。