【问题标题】:trigger config transformation in TFS 2010 or msbuild在 TFS 2010 或 msbuild 中触发配置转换
【发布时间】:2010-12-24 10:30:54
【问题描述】:

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

我需要一种方法来告诉 TFS 构建代理执行转换。我有点希望它在发现配置转换文件(web.qa-release.config、web.production-release.config 等......)后才能工作。但事实并非如此。

我有一个构建正确配置(qa-release、production-release 等)的 TFS 构建定义,并且我有一些在这些定义中构建的特定 .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 以启动转换并删除单个转换文件的恶作剧?

【问题讨论】:

    标签: .net tfs msbuild continuous-integration config


    【解决方案1】:

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

    1. 转到团队资源管理器 > 构建
    2. 编辑您的构建定义(或新建)
    3. 在“流程”步骤下,有一个“要构建的配置”设置。

    在我的例子中,我专门为 CI 设置了一个配置,然后执行正确的 web.config 转换。确保您已添加“CI”转换文件,您应该一切顺利。

    【讨论】:

    • 另请注意,web.config 转换仅在部署期间调用,而不是在标准构建期间调用。
    • 我有一个发布配置(AnyCpu|Release),但构建中的 Web.config 没有改变。
    • 错了,那些“配置”是构建配置,不是配置文件。
    【解决方案2】:

    我终于设法让它工作了。我正在使用 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,因为我也需要这样做......
    • 我们刚刚升级到 TFS 2010。执行升级的顾问设置了某种向后兼容的工作流程,现在使用我们现有的 TFSBuild.proj 文件。当然,我们以后会后悔的,但这仍然适用于我们现在的 Team Build 2010...
    • 实际上,向后兼容的东西是在升级过程中自动安装的。 MS 只为这种情况保留了它。
    【解决方案3】:

    要在 WorkFlow 中执行此操作,您必须创建一个自定义活动。有一篇很好的文章here

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

    完成后,添加这两个类:

    首先,有一个存根:

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

    【讨论】:

    • 这太复杂了,无法执行已经内置的操作。您可能希望看到已接受的答案
    【解决方案4】:

    我找到了另一种方法来完成此任务,而不是创建自定义活动。您只需要修改正在构建的 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 在 TFS 进行构建时对 web.config 进行转换。

    更多详情请访问Kevin Daly's Blog

    【讨论】:

      【解决方案5】:

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

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

      来自链接(以防它被移动/404/等):

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

      <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 应该有一个“构建 “内容”的行动”

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

      【讨论】:

      • 这具有与 TFS 之外的其他构建引擎一起工作的额外优势 - 例如,我正在使用带有 Uppercut 的 CC.NET。
      【解决方案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 目标” - 如果您使用特定名称“ProjectName.wpp.targets”命名另一个 MSBuild 文件,您可以控制整个网站发布过程。在复制发布的网站输出时,我们使用它来删除 -vsdoc javascript 文档文件:

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

      如上所述,您最好完全从构建中忽略生产 web.configs。我们将转换直接放在我们的生产部署机器上,并在部署应用程序时使用 powershell 进行转换。

      【讨论】:

        猜你喜欢
        • 2011-02-10
        • 2011-05-02
        • 1970-01-01
        • 1970-01-01
        • 2011-11-04
        • 1970-01-01
        • 2011-05-11
        • 2011-04-19
        • 1970-01-01
        相关资源
        最近更新 更多