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

发布于 2024-08-14 03:53:36 字数 828 浏览 4 评论 0原文

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

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

我有一个 TFS 构建定义,可以构建正确的配置(qa 发布、生产发布等),并且我有一些在这些定义中构建的特定 .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 中以启动转换并消除各个转换文件?

I'm attempting to make use of configuration transformations in a continuous integration environment.

I need a way to tell the TFS build agent to perform the transformations. I was kind of hoping it would just work after discovering the config transform files (web.qa-release.config, web.production-release.config, etc...). But it doesn't.

I have a TFS build definition that builds the right configurations (qa-release, production-release, etc...) and I have some specific .proj files that get built within these definitions and those contain some environment specific parameters eg:

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

I know from the output that the correct configurations are being built. Now I just need to learn how to trigger the config transformations. Is there some hocus pocus that I can add to the final .proj in the build to kick off the transform and blow away the individual transform files?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

走野 2024-08-21 03:53:36

我找到了另一种方法来完成此任务,而不是创建自定义活动。您只需要修改正在构建的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 的博客

I found another way to accomplish this instead of creating a custom activity. You just need to modify the visual studio project file of the web application that is being built.

Add the following (a placeholder for the 'AfterBuild' target can be found towards the end of the project file):

<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> 

Then you just have to add /p:IsAutoBuild="True" to the 'MSBuild Arguments' field found in the 'Advanced' section of the build definition.

This will force TFS 2010 to do the transformations on the web.config when TFS does the build.

More details can be found on Kevin Daly's Blog.

∝单色的世界 2024-08-21 03:53:36

我终于成功地让这个工作了。我正在使用 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 复制到您需要的任何位置。

I finally managed to get this working. I'm using TFS 2008, but also using MSBuild 4.0 so it should work for you.

First, add this import to TFSBuild.proj:

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

Next, add a BeforeDropBuild target:

<Target Name="BeforeDropBuild">
  <TransformXml Source="$(SolutionRoot)\MySite\Web.config"
    Transform="$(SolutionRoot)\MySite\Web.QA.config"
    Destination="$(OutDir)\_PublishedWebsites\MySite\Web.QA.config.transformed" />
</Target>

You can then copy the Web.QA.config.transformed to wherever you need it to go.

够运 2024-08-21 03:53:36

默认情况下,对于命令行和 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 项目构建过程连接的地方。在 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 进行转换。

The web.config transformation functionality that was added to Website Projects in Visual Studio 2010 is disabled by default for command-line and TFS builds.

There are two relatively straightforward solutions:

Option 1: Edit the build definition and add the following to the "MSBuild Arguments" field:

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

UseWPP_CopyWebApplication will cause the new Web Publishing Pipeline (WPP) to be activated for the build. WPP performs the web.config transformations and can also be used to block things such as .PDB files from being copied to the bin folder.

Option 2: Both MSBuild and WPP are fully extensible. Create a new XML file in the same directory as your project and use the ".targets" extension - for example, ProjectName.custom.targets. Place the following MSBuild code into the targets file:

<?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>

Right click on your website and select "Unload project". Right click on the unloaded project and select Edit. Scroll to the bottom of the project file and look for these lines:

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

These lines are where the C# and Web Project build process gets hooked up. Insert an import to your custom build extensions (target file) immediately before the CSharp import:

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

That's it - you're good to go. The MSBuild customization method is a bit more work to set up, but the benefit is that you can use the new targets file to "hook" into the build process and have much more control over how your build happens on the server. For example, you could hook in tasks to perform CSS and JS compression.

I'd also recommend looking at "wpp targets" - if you name another MSBuild file with the specific name "ProjectName.wpp.targets" you can control the entire website publishing process. We use it to remove the -vsdoc javascript documentation files as the published website output is copied:

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

Everything said, you're probably better off leaving out the production web.configs from your build entirely. We place the transforms directly onto our production deployment machine and use powershell to transform as we deploy the application.

著墨染雨君画夕 2024-08-21 03:53:36

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

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

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

这是我解决这个问题的方法。关键
是编辑 *.csproj 文件
网站并将以下内容添加到
AfterBuild 目标(确保将
上面的评论结束)。这是为了
我们在团队中的网站建设项目
基础服务器。

<目标名称=“AfterBuild”>
    

要保留 web.debug.config,
web.release.config 等...从
发布时一定要设置“Build
属性窗口中的“操作”
每个配置转换
文件设置为“无”。仅主要
web.config 应该有一个“构建
“内容”的“操作”

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

Here's an easier answer for you. :)

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

From the link (in case it gets moved/404/etc):

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".

青衫负雪 2024-08-21 03:53:36

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

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

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

All you should need to do is setup which configuration should be used within the TFS build definition.

  1. Goto Team Explorer > Builds
  2. Edit your build definition (or create new)
  3. Under the "Process" step there is a settings for "Configurations to Build".

In my case I have setup a configuration specifically for CI that then performs the correct web.config transformations. Ensure that you have added the "CI" transform file and you should be good to go.

蛮可爱 2024-08-21 03:53:36

要在工作流中执行此操作,您必须创建一个自定义活动。有一篇关于它的很好的文章 此处

对于此特定活动,您需要创建活动项目(将其从.Net 4 客户端配置文件更改为 .Net 4)并引用 GAC 中的 Microsoft.Build.FrameworkMicrosoft.Build.Utilities.v4.0,然后引用 Microsoft.Web来自 %programfiles%\msbuild\Microsoft\VisualStudio\v10.0\WebApplications 的.Publishing.Tasks(如果您使用的是 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 函数会锁定源配置文件,直到构建过程完成。

To do this in WorkFlow, you have to create a Custom Activity. There's quite a good article about it here

For this specific activity you need to create and Activity project (change it from .Net 4 Client profile to .Net 4) and reference Microsoft.Build.Framework and Microsoft.Build.Utilities.v4.0 from the GAC and then Microsoft.Web.Publishing.Tasks from %programfiles%\msbuild\Microsoft\VisualStudio\v10.0\WebApplications (%programfiles(x86)% if you're on a 64bit system).

When that's done, you add these two classes:

First, there's a stub:

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(); }
    }
}

Then theres the activity class it self:

[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);
        }
    }
}

The reason for the BuildEngineStub is that the TransformXml class uses it to do logging.

The only thing you need to be careful about is that the TransformXml.Execute function locks the source configuration file until the build process is finished.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文