16

我正在尝试在持续集成环境中使用配置转换。

我需要一种方法来告诉 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 以启动转换并删除单个转换文件的恶作剧?

4

6 回答 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 在 TFS 进行构建时对 web.config 进行转换。

更多细节可以在Kevin Daly 的博客上找到。

于 2011-02-10T17:59:18.127 回答
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 复制到您需要的任何位置。

于 2010-05-12T02:00:16.353 回答
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-06-02T18:11:43.720 回答
5

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

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

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

这是我解决这个问题的方法。关键是编辑网站上的 *.csproj 文件并将以下内容添加到 AfterBuild 目标(确保将结束注释移到其上方)。这是针对我们在 Team 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 等被发布,请务必在每个配置转换文件的属性窗口中将“构建操作”设置为“无”。只有主 web.config 应该有一个“内容”的“构建操作”

编辑 csproj 文件的一种简单方法是将“PowerCommands for Visual Studio 2010”或“Productivity Power Tools”扩展加载到 Visual Studio 2010,可从 Visual Studio 库中获得。加载后,您只需右键单击解决方案中的项目并选择“卸载项目”。然后您可以再次右键单击并选择“编辑...”以直接编辑 csproj 文件 XML。然后完成后再次右键单击并选择“重新加载项目”。

于 2011-03-10T03:36:00.173 回答
1

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

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

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

于 2010-05-11T14:21:10.263 回答
1

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

对于此特定活动,您需要创建和 Activity 项目(将其从.Net 4 Client 配置文件更改为.Net 4 )并从 GAC 和Microsoft.Web引用Microsoft.Build.FrameworkMicrosoft.Build.Utilities.v4.0 .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函数会锁定源配置文件,直到构建过程完成。

于 2011-01-04T16:49:18.930 回答